-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build(sage_ticket): Build the project app with admin models and
1 parent
f488b95
commit 3be64f3
Showing
29 changed files
with
1,094 additions
and
24 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from .attachment import AttachmentAdmin | ||
from .comment import CommentAdmin | ||
from .department import DepartmentAdmin | ||
from .issue import IssueAdmin | ||
|
||
__all__ = [ | ||
"AttachmentAdmin", | ||
"CommentAdmin", | ||
"DepartmentAdmin", | ||
"IssueAdmin", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from django.contrib import admin | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from sage_ticket.models import Attachment | ||
|
||
|
||
@admin.register(Attachment) | ||
class AttachmentAdmin(admin.ModelAdmin): | ||
list_display = ("name", "issue", "extensions", "file", "created_at") | ||
list_filter = ("extensions", "created_at") | ||
search_fields = ("name", "issue__title") | ||
readonly_fields = ("created_at", "modified_at") | ||
raw_id_fields = ("issue",) | ||
ordering = ("-created_at",) | ||
fieldsets = ( | ||
( | ||
None, | ||
{ | ||
"fields": ("name", "issue", "extensions", "file"), | ||
"description": _( | ||
"Fields related to the attachment, including its name, associated issue, file extension, and the file itself." | ||
), | ||
}, | ||
), | ||
( | ||
_("Timestamps"), | ||
{ | ||
"fields": ("created_at", "modified_at"), | ||
"description": _( | ||
"Auto-generated timestamps indicating when the attachment was created_at and last modified_at." | ||
), | ||
}, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from django.contrib import admin | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from sage_ticket.models import Comment | ||
|
||
|
||
@admin.register(Comment) | ||
class CommentAdmin(admin.ModelAdmin): | ||
list_display = ("title", "user", "issue", "status", "is_unread", "created_at") | ||
list_filter = ("status", "is_unread", "created_at") | ||
search_fields = ("title", "user__username", "issue__title") | ||
readonly_fields = ("created_at", "modified_at") | ||
ordering = ("-created_at",) | ||
|
||
fieldsets = ( | ||
( | ||
None, | ||
{ | ||
"fields": ("title", "user", "issue", "message", "status", "is_unread"), | ||
"description": _( | ||
"Fields related to the comment, including the title, user, associated issue, message content, status, and unread status." | ||
), | ||
}, | ||
), | ||
( | ||
_("Reply"), | ||
{ | ||
"fields": ("replay",), | ||
"classes": ("collapse",), | ||
"description": _("Field for the reply to this comment, if any."), | ||
}, | ||
), | ||
( | ||
_("Timestamps"), | ||
{ | ||
"fields": ("created_at", "modified_at"), | ||
"description": _( | ||
"Auto-generated timestamps indicating when the comment was created_at and last modified_at." | ||
), | ||
}, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from django.contrib import admin | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from sage_ticket.models import Department | ||
|
||
|
||
@admin.register(Department) | ||
class DepartmentAdmin(admin.ModelAdmin): | ||
list_display = ("title", "created_at") | ||
search_fields = ("title",) | ||
readonly_fields = ("created_at", "modified_at") | ||
ordering = ("title",) | ||
autocomplete_fields = ("member",) | ||
fieldsets = ( | ||
( | ||
None, | ||
{ | ||
"fields": ("title", "description"), | ||
"description": _( | ||
"Fields related to the department, including its title and description." | ||
), | ||
}, | ||
), | ||
( | ||
_("Members"), | ||
{ | ||
"fields": ("member",), | ||
"description": _( | ||
"Field for the members associated with this department." | ||
), | ||
}, | ||
), | ||
( | ||
_("Timestamps"), | ||
{ | ||
"fields": ("created_at", "modified_at"), | ||
"description": _( | ||
"Auto-generated timestamps indicating when the department was created_at and last modified_at." | ||
), | ||
}, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from django.contrib import admin | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
from sage_ticket.models import Attachment, Comment, Department, Issue | ||
|
||
|
||
class AttachmentInline(admin.TabularInline): | ||
model = Attachment | ||
extra = 1 | ||
fields = ("name", "extensions", "file") | ||
show_change_link = True | ||
|
||
|
||
class CommentInline(admin.TabularInline): | ||
model = Comment | ||
extra = 1 | ||
fields = ("title", "user", "message", "is_unread") | ||
show_change_link = True | ||
|
||
|
||
class DepartmentInline(admin.TabularInline): | ||
model = Department | ||
extra = 1 | ||
fields = ("title", "description", "member") | ||
show_change_link = True | ||
|
||
|
||
@admin.register(Issue) | ||
class IssueAdmin(admin.ModelAdmin): | ||
inlines = [AttachmentInline, CommentInline] | ||
list_display = ("subject", "state", "created_at") | ||
list_filter = ("state", "created_at") | ||
search_fields = ("subject", "message") | ||
ordering = ("-created_at",) | ||
readonly_fields = ("created_at", "modified_at") | ||
fieldsets = ( | ||
( | ||
None, | ||
{ | ||
"fields": ("subject", "message", "state", "department", "raised_by"), | ||
"description": _( | ||
"Fields related to the issue, including the subject, description, and current state." | ||
), | ||
}, | ||
), | ||
( | ||
_("Details"), | ||
{ | ||
"fields": ("created_at", "modified_at"), | ||
"description": _( | ||
"Auto-generated timestamps indicating when the issue was created_at and last updated." | ||
), | ||
}, | ||
), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class TicketConfig(AppConfig): | ||
default_auto_field = "django.db.models.BigAutoField" | ||
name = "sage_ticket" | ||
|
||
def ready(self) -> None: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from django.conf import settings | ||
from django.core.checks import Error, register | ||
|
||
|
||
@register() | ||
def check_installed_apps(app_configs, **kwargs): | ||
errors = [] | ||
required_apps = [ | ||
"sage_ticket", | ||
] | ||
|
||
for app in required_apps: | ||
if app not in settings.INSTALLED_APPS: | ||
errors.append( | ||
Error( | ||
f"'{app}' is missing in INSTALLED_APPS.", | ||
hint=f"Add '{app}' to INSTALLED_APPS in your settings.", | ||
obj=settings, | ||
id=f"sage_invoice.E00{required_apps.index(app) + 1}", | ||
) | ||
) | ||
|
||
return errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .state import TicketState | ||
|
||
__all__ = ["TicketState"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,318 @@ | ||
import abc | ||
|
||
from sage_ticket.helper.choice import TicketStateEnum | ||
from sage_ticket.helper.exception import ( | ||
InvalidHoldStateOperation, | ||
InvalidNewStateOperation, | ||
InvalidOpenStateOperation, | ||
InvalidPendingStateOperation, | ||
InvalidResolvedStateOperation, | ||
) | ||
|
||
|
||
class State(abc.ABC): | ||
"""Abstract base class representing the state of a ticket in a state | ||
machine. | ||
This class defines the interface for different states and provides a way | ||
to set the current ticket attribute. Each concrete state class must | ||
implement the abstract methods to handle state-specific behavior. | ||
Attributes: | ||
_current_ticket (TicketState): Holds the reference to the ticket's | ||
current state. | ||
Methods: | ||
set_ticket_attribute(ticket): Sets the current ticket reference. | ||
new(): Abstract method for handling the transition to the 'new' state. | ||
open(): Abstract method for handling the transition to the 'open' state. | ||
pending(): Abstract method for handling the transition to the 'pending' state. | ||
hold(): Abstract method for handling the transition to the 'hold' state. | ||
resolved(): Abstract method for handling the transition to the 'resolved' state. | ||
closed(): Abstract method for handling the transition to the 'closed' state. | ||
""" | ||
|
||
_current_ticket = None | ||
|
||
def set_ticket_attribute(self, ticket): | ||
"""Sets the ticket attribute to the given ticket instance. | ||
Args: | ||
ticket (TicketState): The ticket instance to associate with the state. | ||
""" | ||
self._current_ticket = ticket | ||
|
||
@abc.abstractmethod | ||
def new(self): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def open(self): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def pending(self): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def hold(self): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def resolved(self): | ||
raise NotImplementedError | ||
|
||
@abc.abstractmethod | ||
def closed(self): | ||
raise NotImplementedError | ||
|
||
|
||
class TicketState: | ||
"""Manages the state of a ticket and provides methods to transition between | ||
states. | ||
This class uses the State design pattern to allow different states of a ticket | ||
to be represented by different classes that share a common interface. | ||
Attributes: | ||
_state (State): The current state of the ticket. | ||
Methods: | ||
set_state(state): Sets the current state of the ticket. | ||
set_new(): Transitions the ticket to the 'new' state. | ||
set_open(): Transitions the ticket to the 'open' state. | ||
set_pending(): Transitions the ticket to the 'pending' state. | ||
set_hold(): Transitions the ticket to the 'hold' state. | ||
set_resolved(): Transitions the ticket to the 'resolved' state. | ||
set_closed(): Transitions the ticket to the 'closed' state. | ||
show_state(): Returns the current state by executing the corresponding | ||
state method. | ||
""" | ||
|
||
_state = None | ||
|
||
def __init__(self, state): | ||
self.set_state(state) | ||
|
||
def set_state(self, state): | ||
"""Sets the current state of the ticket and updates the state's ticket | ||
reference. | ||
Args: | ||
state (State): The state to set as the current state. | ||
""" | ||
self._state = state | ||
self._state.set_ticket_attribute(self) | ||
|
||
def set_new(self): | ||
"""Transitions the ticket to the 'new' state.""" | ||
self._state.new() | ||
|
||
def set_open(self): | ||
"""Transitions the ticket to the 'open' state.""" | ||
self._state.open() | ||
|
||
def set_pending(self): | ||
"""Transitions the ticket to the 'pending' state.""" | ||
self._state.pending() | ||
|
||
def set_hold(self): | ||
"""Transitions the ticket to the 'hold' state.""" | ||
self._state.hold() | ||
|
||
def set_resolved(self): | ||
"""Transitions the ticket to the 'resolved' state.""" | ||
self._state.resolved() | ||
|
||
def set_closed(self): | ||
"""Transitions the ticket to the 'closed' state.""" | ||
self._state.closed() | ||
|
||
def show_state(self): | ||
"""Executes the method corresponding to the current state and returns | ||
the result. | ||
This method uses the class name of the current state to determine the | ||
appropriate method to execute. | ||
Returns: | ||
The result of the executed state method. | ||
""" | ||
state_map = { | ||
"NewState": self._state.new, | ||
"OpenState": self._state.open, | ||
"PendingState": self._state.pending, | ||
"HoldState": self._state.hold, | ||
"ResolvedState": self._state.resolved, | ||
"ClosedState": self._state.closed, | ||
} | ||
show_current_state = state_map.get(self._state.__class__.__name__) | ||
|
||
return show_current_state() | ||
|
||
|
||
class NewState(State): | ||
"""Represents the 'new' state of a ticket. | ||
This class implements the behavior specific to the 'new' state, | ||
including transitions to other states. | ||
""" | ||
|
||
def new(self): | ||
return TicketStateEnum.NEW | ||
|
||
def open(self): | ||
return self._current_ticket.set_state(OpenState()) | ||
|
||
def pending(self): | ||
raise InvalidPendingStateOperation() | ||
|
||
def hold(self): | ||
raise InvalidHoldStateOperation() | ||
|
||
def resolved(self): | ||
raise InvalidResolvedStateOperation() | ||
|
||
def closed(self): | ||
return self._current_ticket.set_state(ClosedState()) | ||
|
||
|
||
class OpenState(State): | ||
"""Represents the 'open' state of a ticket. | ||
This class implements the behavior specific to the 'open' state, | ||
including transitions to other states. | ||
""" | ||
|
||
def new(self): | ||
raise InvalidNewStateOperation() | ||
|
||
def open(self): | ||
return TicketStateEnum.OPEN | ||
|
||
def pending(self): | ||
return self._current_ticket.set_state(PendingState()) | ||
|
||
def hold(self): | ||
raise InvalidHoldStateOperation() | ||
|
||
def resolved(self): | ||
raise InvalidResolvedStateOperation() | ||
|
||
def closed(self): | ||
return self._current_ticket.set_state(ClosedState()) | ||
|
||
|
||
class PendingState(State): | ||
"""Represents the 'pending' state of a ticket. | ||
This class implements the behavior specific to the 'pending' state, | ||
including transitions to other states. | ||
""" | ||
|
||
def new(self): | ||
raise InvalidNewStateOperation() | ||
|
||
def open(self): | ||
raise InvalidOpenStateOperation() | ||
|
||
def pending(self): | ||
return TicketStateEnum.PENDING | ||
|
||
def hold(self): | ||
return self._current_ticket.set_state(HoldState()) | ||
|
||
def resolved(self): | ||
raise InvalidResolvedStateOperation() | ||
|
||
def closed(self): | ||
return self._current_ticket.set_state(ClosedState()) | ||
|
||
|
||
class HoldState(State): | ||
"""Represents the 'hold' state of a ticket. | ||
This class implements the behavior specific to the 'hold' state, | ||
including transitions to other states. | ||
""" | ||
|
||
def new(self): | ||
raise InvalidNewStateOperation() | ||
|
||
def open(self): | ||
raise InvalidOpenStateOperation() | ||
|
||
def pending(self): | ||
raise InvalidPendingStateOperation() | ||
|
||
def hold(self): | ||
return TicketStateEnum.HOLD | ||
|
||
def resolved(self): | ||
return self._current_ticket.set_state(ResolvedState()) | ||
|
||
def closed(self): | ||
return self._current_ticket.set_state(ClosedState()) | ||
|
||
|
||
class ResolvedState(State): | ||
"""Represents the 'resolved' state of a ticket. | ||
This class implements the behavior specific to the 'resolved' state, | ||
including transitions to other states. | ||
""" | ||
|
||
def new(self): | ||
raise InvalidNewStateOperation() | ||
|
||
def open(self): | ||
raise InvalidOpenStateOperation | ||
|
||
def pending(self): | ||
raise InvalidPendingStateOperation | ||
|
||
def hold(self): | ||
raise InvalidHoldStateOperation | ||
|
||
def resolved(self): | ||
return TicketStateEnum.RESOLVED | ||
|
||
def closed(self): | ||
return self._current_ticket.set_state(ClosedState()) | ||
|
||
|
||
class ClosedState(State): | ||
"""Represents the 'closed' state of a ticket. | ||
This class implements the behavior specific to the 'closed' state, | ||
including transitions to other states. | ||
""" | ||
|
||
def new(self): | ||
raise InvalidNewStateOperation() | ||
|
||
def open(self): | ||
return self._current_ticket.set_state(OpenState()) | ||
|
||
def pending(self): | ||
raise InvalidPendingStateOperation() | ||
|
||
def hold(self): | ||
raise InvalidHoldStateOperation() | ||
|
||
def resolved(self): | ||
raise InvalidResolvedStateOperation() | ||
|
||
def closed(self): | ||
return TicketStateEnum.CLOSED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from .choice import ExtensionsEnum, SeverityEnum, StatusEnum, TicketStateEnum | ||
|
||
__all__ = [ | ||
"SeverityEnum", | ||
"TicketStateEnum", | ||
"ExtensionsEnum", | ||
"StatusEnum", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from django.db import models | ||
|
||
|
||
class SeverityEnum(models.TextChoices): | ||
"""SeverityEnum is an enumeration that represents different levels of | ||
severity for issues or tasks. | ||
It is used to categorize the criticality or importance of a given | ||
item. | ||
""" | ||
|
||
HIGH = ("high", "High") | ||
MEDIUM = ("medium", "Medium") | ||
LOW = ("low", "Low") | ||
|
||
|
||
class TicketStateEnum(models.TextChoices): | ||
"""TicketStateEnum is an enumeration that represents the different states | ||
that a ticket can have during its lifecycle in a ticketing or issue | ||
tracking system.""" | ||
|
||
NEW = ("new", "New") | ||
OPEN = ("open", "Open") | ||
PENDING = ("pending", "Pending") | ||
HOLD = ("hold", "Hold") | ||
RESOLVED = ("resolved", "Resolved") | ||
CLOSED = ("closed", "Closed") | ||
|
||
|
||
class ExtensionsEnum(models.TextChoices): | ||
"""ExtensionsEnum is an enumeration that represents different file | ||
extensions that may be associated with attachments or other resources in | ||
the system.""" | ||
|
||
pdf = ("pdf", "PDF") | ||
jpg = ("jpg", "JPEG") | ||
png = ("png", "PNG") | ||
webp = ("webp", "WebP") | ||
|
||
|
||
class StatusEnum(models.TextChoices): | ||
"""StatusEnum is an enumeration that represents the status of a response or | ||
inquiry in a communication or ticketing system.""" | ||
|
||
ANSWERED = ("answered", "Answered") | ||
UNANSWERED = ("unanswered", "Unanswered") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
class InvalidStateException(Exception): | ||
"""Base class for exceptions in this module.""" | ||
|
||
pass | ||
|
||
|
||
class InvalidOpenStateOperation(InvalidStateException): | ||
def __init__( | ||
self, message=" SYSTEM_ERROR: Cannot change current state to Open state" | ||
): | ||
self.message = message | ||
super().__init__(self.message) | ||
|
||
|
||
class InvalidNewStateOperation(InvalidStateException): | ||
def __init__( | ||
self, message=" SYSTEM_ERROR: Cannot change current state to New state" | ||
): | ||
self.message = message | ||
super().__init__(self.message) | ||
|
||
|
||
class InvalidPendingStateOperation(InvalidStateException): | ||
def __init__( | ||
self, message=" SYSTEM_ERROR: Cannot change current state to pending state" | ||
): | ||
self.message = message | ||
super().__init__(self.message) | ||
|
||
|
||
class InvalidHoldStateOperation(InvalidStateException): | ||
def __init__( | ||
self, message=" SYSTEM_ERROR: Cannot change current state to hold state" | ||
): | ||
self.message = message | ||
super().__init__(self.message) | ||
|
||
|
||
class InvalidResolvedStateOperation(InvalidStateException): | ||
def __init__( | ||
self, message=" SYSTEM_ERROR: Cannot change current state to resolved state" | ||
): | ||
self.message = message | ||
super().__init__(self.message) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from .attachment import Attachment | ||
from .comment import Comment | ||
from .department import Department | ||
from .issue import Issue | ||
|
||
__all__ = [ | ||
"Attachment", | ||
"Comment", | ||
"Issue", | ||
"Department", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from django.db import models | ||
from django.utils.translation import gettext_lazy as _ | ||
from sage_tools.mixins.models import TimeStampMixin | ||
|
||
from sage_ticket.helper import ExtensionsEnum | ||
|
||
|
||
class Attachment(TimeStampMixin): | ||
"""Model to represent attachments related to issues.""" | ||
|
||
name = models.CharField( | ||
max_length=255, | ||
verbose_name=_("Name"), | ||
help_text=_("The name of the attachment."), | ||
db_comment="The name of the attachment.", | ||
) | ||
issue = models.ForeignKey( | ||
"Issue", | ||
on_delete=models.CASCADE, | ||
verbose_name=_("Issue"), | ||
help_text=_("The issue to which this attachment is related."), | ||
db_comment="The issue to which this attachment is related.", | ||
) | ||
extensions = models.CharField( | ||
choices=ExtensionsEnum.choices, | ||
max_length=20, | ||
verbose_name=_("Extension"), | ||
help_text=_("The file extension of the attachment."), | ||
db_comment="The file extension of the attachment.", | ||
) | ||
file = models.FileField( | ||
_("Issue file"), | ||
upload_to="media/uploads", | ||
help_text=_("The file that is uploaded."), | ||
db_comment="The file that is uploaded.", | ||
) | ||
|
||
class Meta: | ||
verbose_name = _("Attachment") | ||
verbose_name_plural = _("Attachments") | ||
db_table = "sage_attachment" | ||
|
||
def __repr__(self): | ||
return f"<Attachment(id={self.id}, name={self.name}" | ||
|
||
def __str__(self) -> str: | ||
return self.name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from django.conf import settings | ||
from django.db import models | ||
from django.utils.translation import gettext_lazy as _ | ||
from sage_tools.mixins.models import TimeStampMixin | ||
|
||
from sage_ticket.helper import StatusEnum | ||
|
||
|
||
class Comment(TimeStampMixin): | ||
"""Model to represent comments related to issues.""" | ||
|
||
title = models.CharField( | ||
max_length=255, | ||
verbose_name=_("Title"), | ||
help_text=_("The title of the comment."), | ||
db_comment="The title of the comment.", | ||
) | ||
user = models.ForeignKey( | ||
settings.AUTH_USER_MODEL, | ||
verbose_name=_("User"), | ||
on_delete=models.CASCADE, | ||
help_text=_("The user who made the comment."), | ||
db_comment="The user who made the comment.", | ||
) | ||
issue = models.ForeignKey( | ||
"Issue", | ||
verbose_name=_("Issue"), | ||
on_delete=models.CASCADE, | ||
help_text=_("The issue to which this comment is related."), | ||
db_comment="The issue to which this comment is related.", | ||
) | ||
message = models.TextField( | ||
max_length=255, | ||
verbose_name=_("Message"), | ||
help_text=_("The content of the comment."), | ||
db_comment="The content of the comment.", | ||
) | ||
is_unread = models.BooleanField( | ||
verbose_name=_("Is Unread"), | ||
help_text=_("Indicates if the comment is unread."), | ||
db_comment="Indicates if the comment is unread.", | ||
) | ||
status = models.CharField( | ||
choices=StatusEnum.choices, | ||
max_length=10, | ||
verbose_name=_("Status"), | ||
help_text=_("The status of the comment."), | ||
db_comment="The status of the comment.", | ||
) | ||
replay = models.ForeignKey( | ||
"self", | ||
verbose_name=_("Replay"), | ||
on_delete=models.CASCADE, | ||
null=True, | ||
blank=True, | ||
help_text=_("The comment to which this is a reply."), | ||
db_comment="The comment to which this is a reply.", | ||
) | ||
|
||
class Meta: | ||
verbose_name = _("Comment") | ||
verbose_name_plural = _("Comments") | ||
db_table = "sage_comment" | ||
|
||
def __repr__(self): | ||
return f"<Comment(id={self.id}, title={self.title},user={self.user_id}" | ||
|
||
def __str__(self) -> str: | ||
return self.title |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from django.conf import settings | ||
from django.db import models | ||
from django.utils.translation import gettext_lazy as _ | ||
from sage_tools.mixins.models import TimeStampMixin | ||
|
||
|
||
class Department(TimeStampMixin): | ||
"""Model to represent a department within an organization.""" | ||
|
||
title = models.CharField( | ||
max_length=255, | ||
verbose_name=_("Title"), | ||
help_text=_("The title of the department."), | ||
db_comment="The title of the department.", | ||
) | ||
description = models.TextField( | ||
verbose_name=_("Description"), | ||
help_text=_("A description of the department."), | ||
db_comment="A description of the department.", | ||
) | ||
member = models.ManyToManyField( | ||
settings.AUTH_USER_MODEL, | ||
related_name="users", | ||
verbose_name=_("Members"), | ||
help_text=_("The members of the department."), | ||
db_comment="The members of the department.", | ||
) | ||
|
||
class Meta: | ||
verbose_name = _("Department") | ||
verbose_name_plural = _("Departments") | ||
db_table = "sage_department" | ||
|
||
def __repr__(self): | ||
return f"<Department(id={self.id}, title={self.title})>" | ||
|
||
def __str__(self) -> str: | ||
return self.title |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
from django.conf import settings | ||
from django.core.exceptions import ValidationError | ||
from django.db import models | ||
from django.utils.translation import gettext_lazy as _ | ||
from sage_tools.mixins.models import TimeStampMixin | ||
|
||
from sage_ticket.helper import SeverityEnum, TicketStateEnum | ||
|
||
|
||
class Issue(TimeStampMixin): | ||
"""Model to represent an issue in the django_sage_ticket ticketing | ||
system.""" | ||
|
||
subject = models.CharField( | ||
max_length=255, | ||
verbose_name=_("Subject"), | ||
help_text=_("The subject of the issue."), | ||
db_comment="The subject of the issue.", | ||
) | ||
name = models.CharField( | ||
max_length=255, | ||
verbose_name=_("Name"), | ||
help_text=_("The name of the issue reporter."), | ||
db_comment="The name of the issue reporter.", | ||
) | ||
message = models.TextField( | ||
verbose_name=_("Message"), | ||
help_text=_("The detailed message of the issue."), | ||
db_comment="The detailed message of the issue.", | ||
) | ||
severity = models.CharField( | ||
max_length=20, | ||
choices=SeverityEnum.choices, | ||
verbose_name=_("Severity"), | ||
help_text=_("The severity level of the issue."), | ||
db_comment="The severity level of the issue.", | ||
) | ||
raised_by = models.ForeignKey( | ||
settings.AUTH_USER_MODEL, | ||
verbose_name=_("Raised by"), | ||
on_delete=models.CASCADE, | ||
help_text=_("The user who raised the issue."), | ||
db_comment="The user who raised the issue.", | ||
) | ||
department = models.ForeignKey( | ||
"Department", | ||
verbose_name=_("Department"), | ||
on_delete=models.CASCADE, | ||
help_text=_("The department to which the issue is assigned."), | ||
db_comment="The department to which the issue is assigned.", | ||
) | ||
state = models.CharField( | ||
choices=TicketStateEnum.choices, | ||
max_length=20, | ||
verbose_name=_("State"), | ||
help_text=_("The current state of the issue."), | ||
db_comment="The current state of the issue.", | ||
) | ||
is_unread = models.BooleanField( | ||
verbose_name=_("Is Unread"), | ||
default=False, | ||
help_text=_("Indicates if the issue is unread."), | ||
db_comment="Indicates if the issue is unread.", | ||
) | ||
is_archive = models.BooleanField( | ||
verbose_name=_("Is Archive"), | ||
default=False, | ||
help_text=_("Indicates if the issue is archived."), | ||
db_comment="Indicates if the issue is archived.", | ||
) | ||
|
||
class Meta: | ||
verbose_name = _("Issue") | ||
verbose_name_plural = _("Issues") | ||
db_table = "sage_issue" | ||
constraints = [ | ||
models.CheckConstraint( | ||
name="issue_state", check=models.Q(state__in=TicketStateEnum.values) | ||
) | ||
] | ||
|
||
def clean(self): | ||
"""Validate the issue's state transitions and ensure the state value is | ||
valid.""" | ||
super().clean() | ||
|
||
if self.state not in TicketStateEnum.values: | ||
raise ValidationError(_("Invalid state value")) | ||
|
||
current_state = None | ||
if self.pk is not None: # check if data is being updated | ||
current_state = Issue.objects.get(pk=self.pk).state | ||
|
||
valid_states = Issue.get_valid_states() | ||
if current_state is not None and self.state not in valid_states.get( | ||
current_state, [] | ||
): | ||
raise ValidationError( | ||
_(f"Invalid state transition from {current_state} to {self.state}") | ||
) | ||
|
||
@staticmethod | ||
def get_valid_states(): | ||
"""Get the valid state transitions for an issue.""" | ||
valid_state = { | ||
"new": ["open", "closed"], | ||
"open": ["pending", "closed"], | ||
"pending": ["hold", "closed"], | ||
"hold": ["resolved", "closed"], | ||
"resolved": ["closed"], | ||
"closed": ["new"], | ||
} | ||
return valid_state | ||
|
||
def __repr__(self): | ||
return f"<Issue(id={self.id}, subject={self.subject}, state={self.state})>" | ||
|
||
def __str__(self): | ||
return self.subject |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .ticket import TicketDataGenerator | ||
|
||
__all__ = ["TicketDataGenerator"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import functools | ||
import os | ||
import random | ||
from typing import Any, List | ||
|
||
from django.conf import settings | ||
from django.contrib.auth import get_user_model | ||
from django.core.files.uploadedfile import SimpleUploadedFile | ||
from mimesis import Person, Text | ||
from mimesis.locales import Locale | ||
from tqdm import tqdm | ||
|
||
from sage_ticket.helper import ExtensionsEnum, SeverityEnum, StatusEnum, TicketStateEnum | ||
from sage_ticket.models import Attachment, Comment, Department, Issue | ||
|
||
User = get_user_model() | ||
|
||
|
||
class TicketDataGenerator: | ||
def __init__(self): | ||
self.person = Person(Locale.EN) | ||
self.text = Text(Locale.EN) | ||
|
||
def create_users(self, total): | ||
objs = [ | ||
User( | ||
username=f"{self.person.username()}{i}", | ||
email=self.person.email(domains=["sageteam.org", "radin.com"]), | ||
password=self.person.password(length=12, hashed=True), | ||
) | ||
for i in tqdm(range(total)) | ||
] | ||
users = User.objects.bulk_create(objs, batch_size=1000) | ||
return users | ||
|
||
def create_department(self, total): | ||
objs = [ | ||
Department( | ||
description=self.text.text(quantity=5), | ||
title=self.text.title(), | ||
) | ||
for _ in range(total) | ||
] | ||
departments = Department.objects.bulk_create(objs) | ||
return departments | ||
|
||
def create_comment(self, total, users, issues): | ||
objs = [ | ||
Comment( | ||
user=random.choice(users), | ||
issue=random.choice(issues), | ||
message=self.text.text(quantity=3), | ||
title=self.text.title(), | ||
status=random.choice(StatusEnum.choices)[0], | ||
is_unread=True, | ||
) | ||
for i in tqdm(range(total)) | ||
] | ||
comments = Comment.objects.bulk_create(objs) | ||
return comments | ||
|
||
def create_issue(self, total, users, departments): | ||
objs = [ | ||
Issue( | ||
raised_by=random.choice(users), | ||
message=self.text.text(quantity=3), | ||
department=random.choice(departments), | ||
subject=self.text.title(), | ||
state=random.choice(TicketStateEnum.choices)[0], | ||
severity=random.choice(SeverityEnum.choices)[0], | ||
) | ||
for i in tqdm(range(total)) | ||
] | ||
issues = Issue.objects.bulk_create(objs) | ||
return issues | ||
|
||
def create_attachment(self, total, issues): | ||
files = self.get_random_f() | ||
objs = [ | ||
Attachment( | ||
issue=random.choice(issues), | ||
name=self.text.text(quantity=1), | ||
file=SimpleUploadedFile( | ||
name=random.choice(files)[0], content=random.choice(files)[1] | ||
), | ||
extensions=random.choice(ExtensionsEnum.choices)[0], | ||
) | ||
for i in tqdm(range(total)) | ||
] | ||
attachments = Attachment.objects.bulk_create(objs) | ||
return attachments | ||
|
||
def get_random_f(self): | ||
demo_pic_dir_path = os.path.join( | ||
settings.BASE_DIR, | ||
"media", | ||
"demo", | ||
) | ||
files = [] | ||
|
||
for root, dirs, files in os.walk(demo_pic_dir_path, topdown=False): | ||
for name in files: | ||
pic_path = os.path.join(root, name) | ||
with open(pic_path, mode="rb") as demo_pic: | ||
picture = demo_pic.read() | ||
data = (pic_path, picture) | ||
files.append(data) | ||
return files | ||
|
||
def add_2_m_m(self, objs: List[Any], target_field: str, item_per_obj: int, item): | ||
attr = getattr(item, target_field) | ||
try: | ||
items_to_add = list(map(lambda _: random.choice(objs), range(item_per_obj))) | ||
except IndexError as exc: | ||
print(exc) | ||
raise IndexError("objs are empty") | ||
|
||
attr.add(*items_to_add) | ||
|
||
def join_members(self, departments, members, total): | ||
joiner = functools.partial(self.add_2_m_m, members, "member", total) | ||
result = list(tqdm(map(joiner, departments))) | ||
print(result) | ||
return departments |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.db.models import Manager | ||
|
||
from ..queryset import TicketQueryAccess | ||
|
||
|
||
class DataAccessLayerManager(Manager): | ||
def get_queryset(self): | ||
return TicketQueryAccess(self.model, using=self._db) | ||
|
||
def find_publisher(self, publisher): | ||
return self.get_queryset().find_publisher(publisher) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .ticket import TicketQueryAccess | ||
|
||
__all__ = ["TicketQueryAccess"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django.db.models import QuerySet | ||
|
||
|
||
class TicketQueryAccess(QuerySet): | ||
def get_actives(self): | ||
return self.filter(is_unread=True) | ||
|
||
def get_archive(self): | ||
return self.filter(is_unread=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters