Skip to content

Commit

Permalink
build(sage_ticket): Build the project app with admin models and
Browse files Browse the repository at this point in the history
radinceorc committed Aug 29, 2024
1 parent f488b95 commit 3be64f3
Showing 29 changed files with 1,094 additions and 24 deletions.
Empty file added sage_ticket/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions sage_ticket/admin/__init__.py
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",
]
34 changes: 34 additions & 0 deletions sage_ticket/admin/attachment.py
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."
),
},
),
)
42 changes: 42 additions & 0 deletions sage_ticket/admin/comment.py
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."
),
},
),
)
42 changes: 42 additions & 0 deletions sage_ticket/admin/department.py
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."
),
},
),
)
55 changes: 55 additions & 0 deletions sage_ticket/admin/issue.py
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."
),
},
),
)
9 changes: 9 additions & 0 deletions sage_ticket/apps.py
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
23 changes: 23 additions & 0 deletions sage_ticket/check.py
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
3 changes: 3 additions & 0 deletions sage_ticket/design/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .state import TicketState

__all__ = ["TicketState"]
318 changes: 318 additions & 0 deletions sage_ticket/design/state.py
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
8 changes: 8 additions & 0 deletions sage_ticket/helper/__init__.py
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",
]
47 changes: 47 additions & 0 deletions sage_ticket/helper/choice.py
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")
44 changes: 44 additions & 0 deletions sage_ticket/helper/exception.py
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.
11 changes: 11 additions & 0 deletions sage_ticket/models/__init__.py
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",
]
47 changes: 47 additions & 0 deletions sage_ticket/models/attachment.py
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
69 changes: 69 additions & 0 deletions sage_ticket/models/comment.py
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
38 changes: 38 additions & 0 deletions sage_ticket/models/department.py
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
119 changes: 119 additions & 0 deletions sage_ticket/models/issue.py
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.
3 changes: 3 additions & 0 deletions sage_ticket/repository/generator/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .ticket import TicketDataGenerator

__all__ = ["TicketDataGenerator"]
124 changes: 124 additions & 0 deletions sage_ticket/repository/generator/ticket.py
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.
11 changes: 11 additions & 0 deletions sage_ticket/repository/manager/ticketing.py
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)
3 changes: 3 additions & 0 deletions sage_ticket/repository/queryset/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .ticket import TicketQueryAccess

__all__ = ["TicketQueryAccess"]
9 changes: 9 additions & 0 deletions sage_ticket/repository/queryset/ticket.py
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)
20 changes: 10 additions & 10 deletions sage_ticket/tests/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -9,19 +9,19 @@
TicketState,
)
from sage_ticket.helper.exception import (
InvalidHoldStateOperation ,
InvalidNewStateOperation ,
InvalidOpenStateOperation ,
InvalidPendingStateOperation ,
InvalidResolvedStateOperation ,
InvalidHoldStateOperation,
InvalidNewStateOperation,
InvalidOpenStateOperation,
InvalidPendingStateOperation,
InvalidResolvedStateOperation,
InvalidStateException,
)


class TestStateExceptions:
def test_invalid_new_state_operation(self):
ticket = TicketState(OpenState())
with pytest.raises(InvalidNewStateOperation ) as excinfo:
with pytest.raises(InvalidNewStateOperation) as excinfo:
ticket.set_new()
assert (
str(excinfo.value)
@@ -30,7 +30,7 @@ def test_invalid_new_state_operation(self):

def test_invalid_open_state_operation(self):
ticket = TicketState(PendingState())
with pytest.raises(InvalidOpenStateOperation ) as excinfo:
with pytest.raises(InvalidOpenStateOperation) as excinfo:
ticket.set_open()
assert (
str(excinfo.value)
@@ -39,7 +39,7 @@ def test_invalid_open_state_operation(self):

def test_invalid_pending_state_operation(self):
ticket = TicketState(HoldState())
with pytest.raises(InvalidPendingStateOperation ) as excinfo:
with pytest.raises(InvalidPendingStateOperation) as excinfo:
ticket.set_pending()
assert (
str(excinfo.value)
@@ -48,7 +48,7 @@ def test_invalid_pending_state_operation(self):

def test_invalid_hold_state_operation(self):
ticket = TicketState(ResolvedState())
with pytest.raises(InvalidHoldStateOperation ) as excinfo:
with pytest.raises(InvalidHoldStateOperation) as excinfo:
ticket.set_hold()
assert (
str(excinfo.value)
@@ -57,7 +57,7 @@ def test_invalid_hold_state_operation(self):

def test_invalid_resolved_state_operation(self):
ticket = TicketState(NewState())
with pytest.raises(InvalidResolvedStateOperation ) as excinfo:
with pytest.raises(InvalidResolvedStateOperation) as excinfo:
ticket.set_resolved()
assert (
str(excinfo.value)
6 changes: 3 additions & 3 deletions sage_ticket/tests/test_repo.py
Original file line number Diff line number Diff line change
@@ -12,15 +12,15 @@ def generator(self):
def test_create_departments(self, generator):
departments = generator.create_department(2)
assert len(departments) == 2
assert Department.objects.count() !=0
assert Department.objects.count() != 0

def test_create_issues(self, generator):
users = generator.create_users(10)
departments = generator.create_department(2)

issues = generator.create_issue(10, users, departments)
assert len(issues) == 10
assert Issue.objects.count() !=0
assert Issue.objects.count() != 0

def test_create_comments(self, generator):
users = generator.create_users(10)
@@ -29,4 +29,4 @@ def test_create_comments(self, generator):

comments = generator.create_comment(10, users, issues)
assert len(comments) == 10
assert Comment.objects.count() !=0
assert Comment.objects.count() != 0
22 changes: 11 additions & 11 deletions sage_ticket/tests/test_state.py
Original file line number Diff line number Diff line change
@@ -10,11 +10,11 @@
)
from sage_ticket.helper.choice import TicketStateEnum
from sage_ticket.helper.exception import (
InvalidHoldStateOperation ,
InvalidNewStateOperation ,
InvalidOpenStateOperation ,
InvalidPendingStateOperation ,
InvalidResolvedStateOperation ,
InvalidHoldStateOperation,
InvalidNewStateOperation,
InvalidOpenStateOperation,
InvalidPendingStateOperation,
InvalidResolvedStateOperation,
)


@@ -39,27 +39,27 @@ def test_valid_transitions(self):
def test_invalid_transitions(self):
ticket = TicketState(NewState())

with pytest.raises(InvalidResolvedStateOperation ):
with pytest.raises(InvalidResolvedStateOperation):
ticket.set_resolved()

ticket.set_open()
with pytest.raises(InvalidNewStateOperation ):
with pytest.raises(InvalidNewStateOperation):
ticket.set_new()

ticket.set_pending()
with pytest.raises(InvalidOpenStateOperation ):
with pytest.raises(InvalidOpenStateOperation):
ticket.set_open()

ticket.set_hold()
with pytest.raises(InvalidPendingStateOperation ):
with pytest.raises(InvalidPendingStateOperation):
ticket.set_pending()

ticket.set_resolved()
with pytest.raises(InvalidHoldStateOperation ):
with pytest.raises(InvalidHoldStateOperation):
ticket.set_hold()

ticket.set_closed()
with pytest.raises(InvalidPendingStateOperation ):
with pytest.raises(InvalidPendingStateOperation):
ticket.set_pending()


0 comments on commit 3be64f3

Please sign in to comment.