diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f3555e..d2e89b6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,8 @@ jobs: run: cp helpdesk/helpdesk/configuration.dev.py helpdesk/helpdesk/configuration.py - name: Static type checking run: make type + - name: Formatting + run: make format-check - name: Lint run: make lint - name: Unit tests diff --git a/.gitignore b/.gitignore index 1d4cec2..e9ad518 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ helpdesk/helpdesk/configuration.py helpdesk/db.sqlite +helpdesk/static teams.csv ### Django ### diff --git a/Makefile b/Makefile index ea0e220..00b098a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean lint type test test-cov +.PHONY: all clean format format-check lint type test test-cov CMD:= PYMODULE:=helpdesk @@ -6,7 +6,15 @@ MANAGEPY:=$(CMD) ./$(PYMODULE)/manage.py APPS:=helpdesk accounts display teams tickets SPHINX_ARGS:=docs/ docs/_build -nWE -all: type test lint +all: type test format lint + +format: + find $(PYMODULE) -name "*.html" | xargs $(CMD) djhtml + $(CMD) ruff format $(PYMODULE) + +format-check: + find $(PYMODULE) -name "*.html" | xargs $(CMD) djhtml --check + $(CMD) ruff format --check $(PYMODULE) lint: $(CMD) ruff check $(PYMODULE) diff --git a/README.md b/README.md index b767d9a..5e81567 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,17 @@ The `Makefile` contains commands that can be used to run tests and linting: - `make test` - Run unit tests - `make type` - Type checking -## SR2023 Deployment +## Deployment -This system was deployed for the helpdesk at the SR2023 competition as an experiment and received positive feedback. It was deployed on a 512MB 1 core machine on [Fly](https://fly.io) with a separate Postgres database server of the same specifications. \ No newline at end of file +This system is deployed using our [Ansible](https://github.com/srobo/ansible/) configuration. + +### Login with Google + +Credentials are configured through the Django admin. OAuth credentials need to be configured as below: + +- User type: Internal (this ensures it's only SR accounts which can be used) +- Scopes: `.../auth/userinfo.email`, `.../auth/userinfo.profile`, `openid` +- Redirect URIs: `https://studentrobotics.org/helpdesk/auth/google/login/callback/` +- Authorised JavaScript origins: `https://studentrobotics.org` + +A [project](https://console.cloud.google.com/home/dashboard?project=helpdesk-419320) exists for this in our Google Cloud account. diff --git a/helpdesk/accounts/forms.py b/helpdesk/accounts/forms.py index 7eaa3aa..e8b6c13 100644 --- a/helpdesk/accounts/forms.py +++ b/helpdesk/accounts/forms.py @@ -9,7 +9,6 @@ class SignupForm(UserCreationForm): - signup_code = forms.CharField( label="Volunteer Signup Code", help_text="This code verifies that you are a volunteer. It can be found in the break room.", @@ -20,7 +19,7 @@ class SignupForm(UserCreationForm): class Meta: model = User fields = ("username",) - field_classes = {'username': UsernameField} + field_classes = {"username": UsernameField} def clean_signup_code(self) -> None: signup_code = self.cleaned_data.get("signup_code") diff --git a/helpdesk/accounts/middleware.py b/helpdesk/accounts/middleware.py index 0fa630e..da2f4e9 100644 --- a/helpdesk/accounts/middleware.py +++ b/helpdesk/accounts/middleware.py @@ -8,7 +8,6 @@ class ProfileMiddleware: - EXCLUDED_PATHS: set[tuple[str | None, str]] = { (None, "account_logout"), ("accounts", "onboarding"), @@ -19,11 +18,13 @@ def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None: def __call__(self, request: HttpRequest) -> HttpResponse: profile_complete = getattr(request.user, "onboarded_at", None) - if all([ - request.user.is_authenticated, - not profile_complete, - self._request_requires_profile(request), - ]): + if all( + [ + request.user.is_authenticated, + not profile_complete, + self._request_requires_profile(request), + ] + ): return redirect("accounts:onboarding") return self.get_response(request) diff --git a/helpdesk/accounts/migrations/0001_create_user_model.py b/helpdesk/accounts/migrations/0001_create_user_model.py index 06ef6d1..5dee94c 100644 --- a/helpdesk/accounts/migrations/0001_create_user_model.py +++ b/helpdesk/accounts/migrations/0001_create_user_model.py @@ -28,7 +28,9 @@ class Migration(migrations.Migration): ( "last_login", models.DateTimeField( - blank=True, null=True, verbose_name="last login", + blank=True, + null=True, + verbose_name="last login", ), ), ( @@ -61,7 +63,9 @@ class Migration(migrations.Migration): ( "email", models.EmailField( - blank=True, max_length=254, verbose_name="email address", + blank=True, + max_length=254, + verbose_name="email address", ), ), ( @@ -83,7 +87,8 @@ class Migration(migrations.Migration): ( "date_joined", models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined", + default=django.utils.timezone.now, + verbose_name="date joined", ), ), ], diff --git a/helpdesk/accounts/migrations/0003_auto_20230318_1405.py b/helpdesk/accounts/migrations/0003_auto_20230318_1405.py index af1ebb5..1585895 100644 --- a/helpdesk/accounts/migrations/0003_auto_20230318_1405.py +++ b/helpdesk/accounts/migrations/0003_auto_20230318_1405.py @@ -4,24 +4,23 @@ class Migration(migrations.Migration): - dependencies = [ - ('accounts', '0002_add_default_ticket_queue'), + ("accounts", "0002_add_default_ticket_queue"), ] operations = [ migrations.RemoveField( - model_name='user', - name='name', + model_name="user", + name="name", ), migrations.AddField( - model_name='user', - name='first_name', - field=models.CharField(blank=True, max_length=150, verbose_name='first name'), + model_name="user", + name="first_name", + field=models.CharField(blank=True, max_length=150, verbose_name="first name"), ), migrations.AddField( - model_name='user', - name='last_name', - field=models.CharField(blank=True, max_length=150, verbose_name='last name'), + model_name="user", + name="last_name", + field=models.CharField(blank=True, max_length=150, verbose_name="last name"), ), ] diff --git a/helpdesk/accounts/migrations/0005_user_onboarded_at.py b/helpdesk/accounts/migrations/0005_user_onboarded_at.py index 8c0ffe9..da58b01 100644 --- a/helpdesk/accounts/migrations/0005_user_onboarded_at.py +++ b/helpdesk/accounts/migrations/0005_user_onboarded_at.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('accounts', '0004_allow_default_ticket_queue_to_be_blank'), + ("accounts", "0004_allow_default_ticket_queue_to_be_blank"), ] operations = [ migrations.AddField( - model_name='user', - name='onboarded_at', + model_name="user", + name="onboarded_at", field=models.DateTimeField(blank=True, null=True), ), ] diff --git a/helpdesk/accounts/models.py b/helpdesk/accounts/models.py index cb2db50..9fbde8a 100644 --- a/helpdesk/accounts/models.py +++ b/helpdesk/accounts/models.py @@ -5,7 +5,6 @@ class User(AbstractUser): - # Helpdesk Specific Fields default_ticket_queue = models.ForeignKey( "tickets.TicketQueue", diff --git a/helpdesk/accounts/views.py b/helpdesk/accounts/views.py index 1c12d36..356eb56 100644 --- a/helpdesk/accounts/views.py +++ b/helpdesk/accounts/views.py @@ -17,7 +17,6 @@ class ProfileUpdateView(LoginRequiredMixin, UpdateView): - model = User fields = ["first_name", "last_name", "default_ticket_queue"] @@ -28,8 +27,8 @@ def get_object(self, queryset: models.QuerySet[User] | None = None) -> User: def get_form(self, form_class: type[BaseModelForm] | None = None) -> BaseModelForm: form = super().get_form(form_class) # Modify generated form to require a name. - form.fields['first_name'].required = True - form.fields['first_name'].label = "Given name" + form.fields["first_name"].required = True + form.fields["first_name"].label = "Given name" return form def get_success_url(self) -> str: @@ -38,7 +37,6 @@ def get_success_url(self) -> str: class OnboardingView(LoginRequiredMixin, UpdateView): - model = User fields = ["first_name", "last_name", "default_ticket_queue"] template_name = "accounts/onboarding.html" @@ -57,8 +55,8 @@ def get_object(self, queryset: models.QuerySet[User] | None = None) -> User: def get_form(self, form_class: type[BaseModelForm] | None = None) -> BaseModelForm: form = super().get_form(form_class) # Modify generated form to require a name. - form.fields['first_name'].required = True - form.fields['first_name'].label = "Given name" + form.fields["first_name"].required = True + form.fields["first_name"].label = "Given name" return form def form_valid(self, form: BaseModelForm) -> HttpResponse: @@ -72,5 +70,5 @@ def get_success_url(self) -> str: class SignupView(CreateView): form_class = SignupForm - success_url = reverse_lazy('account_login') - template_name = 'accounts/signup.html' + success_url = reverse_lazy("account_login") + template_name = "accounts/signup.html" diff --git a/helpdesk/display/apps.py b/helpdesk/display/apps.py index c79d703..1f0c36d 100644 --- a/helpdesk/display/apps.py +++ b/helpdesk/display/apps.py @@ -2,5 +2,5 @@ class DisplayConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'display' + default_auto_field = "django.db.models.BigAutoField" + name = "display" diff --git a/helpdesk/display/views.py b/helpdesk/display/views.py index 62be69c..51f49db 100644 --- a/helpdesk/display/views.py +++ b/helpdesk/display/views.py @@ -7,7 +7,6 @@ class HelpdeskDisplayView(TemplateView): - template_name = "display/helpdesk.html" def get_context_data(self, **kwargs: Any) -> dict[str, Any]: diff --git a/helpdesk/helpdesk/account_adapter.py b/helpdesk/helpdesk/account_adapter.py index c98c143..0e86dd3 100644 --- a/helpdesk/helpdesk/account_adapter.py +++ b/helpdesk/helpdesk/account_adapter.py @@ -4,7 +4,6 @@ class AccountAdapter(DefaultAccountAdapter): - def is_open_for_signup(self, request: HttpRequest) -> bool: """ Checks whether or not the site is open for signups. @@ -17,4 +16,3 @@ def is_open_for_signup(self, request: HttpRequest) -> bool: if request.path.rstrip("/") == reverse("account_signup").rstrip("/"): return False return True - diff --git a/helpdesk/helpdesk/forms.py b/helpdesk/helpdesk/forms.py index 1282692..346b98d 100644 --- a/helpdesk/helpdesk/forms.py +++ b/helpdesk/helpdesk/forms.py @@ -2,6 +2,4 @@ class CommentSubmitForm(forms.Form): - comment = forms.CharField(widget=forms.Textarea(attrs={"rows": "5"})) - diff --git a/helpdesk/helpdesk/settings.py b/helpdesk/helpdesk/settings.py index fb69572..952587d 100644 --- a/helpdesk/helpdesk/settings.py +++ b/helpdesk/helpdesk/settings.py @@ -55,9 +55,7 @@ ADMINS = getattr(configuration, "ADMINS", []) BASE_PATH = getattr(configuration, "BASE_PATH", "") if BASE_PATH: - BASE_PATH = ( - BASE_PATH.strip("/") + "/" - ) # Enforce trailing slash only # pragma: nocover + BASE_PATH = BASE_PATH.strip("/") + "/" # Enforce trailing slash only # pragma: nocover DEBUG = getattr(configuration, "DEBUG", False) EMAIL = getattr(configuration, "EMAIL", {}) SYSTEM_TITLE = getattr(configuration, "SYSTEM_TITLE", "Helpdesk") @@ -101,12 +99,12 @@ "crispy_forms", "crispy_bulma", "django_filters", - 'django_tables2', - 'django_tables2_bulma_template', - 'allauth', - 'allauth.account', - 'allauth.socialaccount', - 'allauth.socialaccount.providers.google', + "django_tables2", + "django_tables2_bulma_template", + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.google", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -123,12 +121,13 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "allauth.account.middleware.AccountMiddleware", "accounts.middleware.ProfileMiddleware", ] AUTHENTICATION_BACKENDS = [ - 'django.contrib.auth.backends.ModelBackend', - 'allauth.account.auth_backends.AuthenticationBackend', + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", ] CORS_ALLOW_ALL_ORIGINS = True @@ -193,7 +192,7 @@ # Authentication URLs LOGIN_URL = f"/{BASE_PATH}auth/login/" -LOGOUT_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = LOGIN_URL # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field @@ -209,16 +208,16 @@ # Django AllAuth -ACCOUNT_ADAPTER = 'helpdesk.account_adapter.AccountAdapter' -ACCOUNT_DEFAULT_HTTP_PROTOCOL = 'https' +ACCOUNT_ADAPTER = "helpdesk.account_adapter.AccountAdapter" +ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" SOCIALACCOUNT_PROVIDERS = { - 'google': { - 'SCOPE': [ - 'profile', - 'email', + "google": { + "SCOPE": [ + "profile", + "email", ], - 'AUTH_PARAMS': { - 'access_type': 'online', + "AUTH_PARAMS": { + "access_type": "online", }, }, } @@ -227,3 +226,34 @@ SRCOMP_HTTP_BASE_URL = getattr(configuration, "SRCOMP_HTTP_BASE_URL", None) VOLUNTEER_SIGNUP_CODE = getattr(configuration, "VOLUNTEER_SIGNUP_CODE") + + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + # Send logs with at least INFO level to the console. + "console": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "verbose", + }, + }, + "formatters": { + "verbose": { + "format": "[%(asctime)s][%(process)d][%(levelname)s][%(name)s] %(message)s", + }, + }, + "loggers": { + "django.request": { + "handlers": ["console"], + "level": "ERROR", + "propagate": False, + }, + "django.security": { + "handlers": ["console"], + "level": "WARNING", + "propagate": False, + }, + }, +} diff --git a/helpdesk/helpdesk/tables.py b/helpdesk/helpdesk/tables.py index 0662c3b..7ffc80a 100644 --- a/helpdesk/helpdesk/tables.py +++ b/helpdesk/helpdesk/tables.py @@ -2,7 +2,6 @@ class SearchTable(tables.Table): - result_type = tables.Column("Result Type") title = tables.URLColumn(accessor="url", text=lambda x: x["title"]) url = tables.URLColumn(verbose_name="Actions", text="View") diff --git a/helpdesk/helpdesk/tests/test_smoke.py b/helpdesk/helpdesk/tests/test_smoke.py index d53ef63..81531d3 100644 --- a/helpdesk/helpdesk/tests/test_smoke.py +++ b/helpdesk/helpdesk/tests/test_smoke.py @@ -11,11 +11,11 @@ def test_authentication_required(client: Client) -> None: resp = client.get("/") assert resp.status_code == 302 - assert resp['Location'] == '/auth/login/?next=/' + assert resp["Location"] == "/auth/login/?next=/" def test_landing_page_redirects(client: Client, admin_user: User) -> None: client.force_login(admin_user) resp = client.get("/") assert resp.status_code == 302 - assert resp['Location'] == '/accounts/onboarding/' + assert resp["Location"] == "/accounts/onboarding/" diff --git a/helpdesk/helpdesk/urls.py b/helpdesk/helpdesk/urls.py index ed0baa1..25de4b4 100644 --- a/helpdesk/helpdesk/urls.py +++ b/helpdesk/helpdesk/urls.py @@ -13,6 +13,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + from django.conf import settings from django.contrib import admin from django.urls import include, path @@ -23,7 +24,7 @@ path(f"{settings.BASE_PATH}", DefaultHomeView.as_view(), name="home"), path(f"{settings.BASE_PATH}search/", SearchView.as_view(), name="search"), path(f"{settings.BASE_PATH}admin/", admin.site.urls), - path(f'{settings.BASE_PATH}auth/', include('allauth.urls')), + path(f"{settings.BASE_PATH}auth/", include("allauth.urls")), path(f"{settings.BASE_PATH}accounts/", include("accounts.urls", namespace="accounts")), path(f"{settings.BASE_PATH}display/", include("display.urls", namespace="display")), path(f"{settings.BASE_PATH}teams/", include("teams.urls", namespace="teams")), diff --git a/helpdesk/helpdesk/utils.py b/helpdesk/helpdesk/utils.py index 75c8c21..c148eb0 100644 --- a/helpdesk/helpdesk/utils.py +++ b/helpdesk/helpdesk/utils.py @@ -6,6 +6,7 @@ ModelT = TypeVar("ModelT", bound=Model) + def get_object_or_none(model: type[ModelT], **kwargs: Any) -> ModelT | None: try: return model.objects.get(**kwargs) # type: ignore[attr-defined] diff --git a/helpdesk/helpdesk/views.py b/helpdesk/helpdesk/views.py index e9f7197..f197c9c 100644 --- a/helpdesk/helpdesk/views.py +++ b/helpdesk/helpdesk/views.py @@ -22,7 +22,8 @@ def get_redirect_url(self, *arg: Any, **kwargs: Any) -> str | None: # Redirect the user to a default queue if they have one if ticket_queue := self.request.user.default_ticket_queue: return reverse_lazy( - "tickets:queue_detail", kwargs={"slug": ticket_queue.slug}, + "tickets:queue_detail", + kwargs={"slug": ticket_queue.slug}, ) else: return reverse_lazy("teams:team_list") @@ -44,19 +45,18 @@ def get_redirect_url(self, *arg: Any, **kwargs: Any) -> str | None: return reverse_lazy("teams:team_list") return reverse_lazy( - "tickets:queue_detail", kwargs={"slug": ticket_queue.slug}, + "tickets:queue_detail", + kwargs={"slug": ticket_queue.slug}, ) class SearchResult(TypedDict): - result_type: Literal["team"] | Literal["ticket"] title: str url: str class SearchView(LoginRequiredMixin, SingleTableMixin, TemplateView): - template_name = "search.html" table_class = SearchTable @@ -77,20 +77,17 @@ def _get_filters(self, q: str) -> dict[type[Ticket] | type[Team], Q]: def get_result_count(self, q: str) -> int: filters = self._get_filters(q) return sum( - [ - model.objects.filter(q_filter).distinct().count() - for model, q_filter in filters.items() - ], + [model.objects.filter(q_filter).distinct().count() for model, q_filter in filters.items()], ) def get_results(self, q: str) -> Generator[SearchResult, None, None]: filters = self._get_filters(q) for team in Team.objects.filter(filters[Team]): - yield SearchResult(result_type='team', title=team.name, url=team.get_absolute_url()) + yield SearchResult(result_type="team", title=team.name, url=team.get_absolute_url()) for ticket in Ticket.objects.filter(filters[Ticket]).distinct(): - yield SearchResult(result_type='ticket', title=ticket.title, url=ticket.get_absolute_url()) + yield SearchResult(result_type="ticket", title=ticket.title, url=ticket.get_absolute_url()) def get_table_data(self) -> Generator[SearchResult, None, None]: q = self._get_query() diff --git a/helpdesk/manage.py b/helpdesk/manage.py index 795763a..4147fbb 100755 --- a/helpdesk/manage.py +++ b/helpdesk/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys diff --git a/helpdesk/pyproject.toml b/helpdesk/pyproject.toml index 85b3ead..fd3d8ba 100644 --- a/helpdesk/pyproject.toml +++ b/helpdesk/pyproject.toml @@ -22,7 +22,10 @@ module = ["django_tables2.*", "allauth.*"] ignore_missing_imports = true [tool.ruff] -select = [ +line-length = 120 + +[tool.ruff.lint] +extend-select = [ "A", "ANN", "B", @@ -41,12 +44,11 @@ select = [ "UP", "W", ] -ignore = [ +extend-ignore = [ "ANN101", # Missing type annotation for `self` in method "ANN401", # Dynamically typed expressions (typing.Any) are disallowed "B009", # Do not call `getattr` with a constant attribute value. + "COM812", # Trailing comma missing, conflicts with black styling "S101", # S101 Use of `assert` detected "N999", # N999 Invalid module name ] - -line-length = 120 diff --git a/helpdesk/teams/admin.py b/helpdesk/teams/admin.py index 4bd4d47..ca44666 100644 --- a/helpdesk/teams/admin.py +++ b/helpdesk/teams/admin.py @@ -4,18 +4,20 @@ class TeamPitLocationAdmin(admin.ModelAdmin): - list_display = ("name", ) + list_display = ("name",) + class TeamCommentAdmin(admin.StackedInline): model = TeamComment extra = 1 - readonly_fields = ('created_at', ) + readonly_fields = ("created_at",) + class TeamAdmin(admin.ModelAdmin): list_display = ("tla", "name", "is_rookie") list_filter = ("is_rookie",) - inlines = (TeamCommentAdmin, ) + inlines = (TeamCommentAdmin,) admin.site.register(TeamPitLocation, TeamPitLocationAdmin) diff --git a/helpdesk/teams/filters.py b/helpdesk/teams/filters.py index b02f9b7..ffce2e9 100644 --- a/helpdesk/teams/filters.py +++ b/helpdesk/teams/filters.py @@ -4,7 +4,6 @@ class TeamFilterset(FilterSet): - is_rookie = filters.BooleanFilter() pit_location = filters.ModelChoiceFilter(queryset=TeamPitLocation.objects.all()) diff --git a/helpdesk/teams/management/commands/import_from_srcomp.py b/helpdesk/teams/management/commands/import_from_srcomp.py index ea8230f..a227548 100644 --- a/helpdesk/teams/management/commands/import_from_srcomp.py +++ b/helpdesk/teams/management/commands/import_from_srcomp.py @@ -5,6 +5,7 @@ DEFAULT_SRCOMP = "https://srcomp.studentrobotics.org/comp-api" + class Command(BaseCommand): help = "Import teams and pit locations from SRComp" # noqa: A003 diff --git a/helpdesk/teams/migrations/0001_create_team_model.py b/helpdesk/teams/migrations/0001_create_team_model.py index a1bcf1d..a74cc91 100644 --- a/helpdesk/teams/migrations/0001_create_team_model.py +++ b/helpdesk/teams/migrations/0001_create_team_model.py @@ -29,7 +29,8 @@ class Migration(migrations.Migration): unique=True, validators=[ django.core.validators.RegexValidator( - "^[A-Z]{3}\\d*$", "Must match TLA format.", + "^[A-Z]{3}\\d*$", + "Must match TLA format.", ), ], verbose_name="TLA", diff --git a/helpdesk/teams/migrations/0002_add_team_pit_locations.py b/helpdesk/teams/migrations/0002_add_team_pit_locations.py index e152dfb..c1e1ad2 100644 --- a/helpdesk/teams/migrations/0002_add_team_pit_locations.py +++ b/helpdesk/teams/migrations/0002_add_team_pit_locations.py @@ -5,24 +5,23 @@ class Migration(migrations.Migration): - dependencies = [ - ('teams', '0001_create_team_model'), + ("teams", "0001_create_team_model"), ] operations = [ migrations.CreateModel( - name='TeamPitLocation', + name="TeamPitLocation", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='Name')), - ('slug', models.CharField(max_length=30, verbose_name='Slug')), + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=100, verbose_name="Name")), + ("slug", models.CharField(max_length=30, verbose_name="Slug")), ], ), migrations.AddField( - model_name='team', - name='pit_location', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to='teams.teampitlocation'), + model_name="team", + name="pit_location", + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to="teams.teampitlocation"), preserve_default=False, ), ] diff --git a/helpdesk/teams/migrations/0003_add_missing_unique_constraint.py b/helpdesk/teams/migrations/0003_add_missing_unique_constraint.py index 81b5e99..a0cd162 100644 --- a/helpdesk/teams/migrations/0003_add_missing_unique_constraint.py +++ b/helpdesk/teams/migrations/0003_add_missing_unique_constraint.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('teams', '0002_add_team_pit_locations'), + ("teams", "0002_add_team_pit_locations"), ] operations = [ migrations.AlterField( - model_name='teampitlocation', - name='slug', - field=models.CharField(max_length=30, unique=True, verbose_name='Slug'), + model_name="teampitlocation", + name="slug", + field=models.CharField(max_length=30, unique=True, verbose_name="Slug"), ), ] diff --git a/helpdesk/teams/models.py b/helpdesk/teams/models.py index 669fbdb..ec78998 100644 --- a/helpdesk/teams/models.py +++ b/helpdesk/teams/models.py @@ -6,7 +6,6 @@ class TeamPitLocation(models.Model): - name = models.CharField("Name", max_length=100) slug = models.CharField("Slug", max_length=30, unique=True) @@ -34,7 +33,7 @@ def __str__(self) -> str: return f"{self.tla} - {self.name}" def get_absolute_url(self) -> str: - return reverse_lazy('teams:team_detail', args=[self.tla]) + return reverse_lazy("teams:team_detail", args=[self.tla]) class TeamComment(models.Model): diff --git a/helpdesk/teams/srcomp.py b/helpdesk/teams/srcomp.py index b9e7553..10935b7 100644 --- a/helpdesk/teams/srcomp.py +++ b/helpdesk/teams/srcomp.py @@ -1,4 +1,5 @@ """A basic client for SRComp HTTP.""" + from __future__ import annotations from json import JSONDecodeError @@ -10,14 +11,12 @@ class ScoreInfo(NamedTuple): - league_pos: int game_score: int league_score: int class SRComp: - def __init__(self, *, base_url: str | None = None) -> None: self._base_url = base_url or settings.SRCOMP_HTTP_BASE_URL @@ -48,4 +47,5 @@ def get_score_info_for_team(self, tla: str) -> ScoreInfo | None: ) return None + srcomp = SRComp() diff --git a/helpdesk/teams/tables.py b/helpdesk/teams/tables.py index a4103eb..fd43bca 100644 --- a/helpdesk/teams/tables.py +++ b/helpdesk/teams/tables.py @@ -4,11 +4,10 @@ class TeamTable(tables.Table): - tla = tables.Column() - name = tables.LinkColumn('teams:team_detail', args=[tables.A('tla')]) + name = tables.LinkColumn("teams:team_detail", args=[tables.A("tla")]) is_rookie = tables.BooleanColumn() - actions = tables.LinkColumn('teams:team_detail', args=[tables.A('tla')], text="View") + actions = tables.LinkColumn("teams:team_detail", args=[tables.A("tla")], text="View") class Meta: model = Team diff --git a/helpdesk/teams/views.py b/helpdesk/teams/views.py index 3faf326..17ab927 100644 --- a/helpdesk/teams/views.py +++ b/helpdesk/teams/views.py @@ -24,7 +24,6 @@ class TicketDetailRedirectView(RedirectView): - pattern_name = "teams:team_detail_tickets" @@ -33,8 +32,8 @@ class TeamListView(LoginRequiredMixin, SingleTableMixin, FilterView): table_class = TeamTable filterset_class = TeamFilterset -class TeamDetailAboutView(LoginRequiredMixin, DetailView): +class TeamDetailAboutView(LoginRequiredMixin, DetailView): model = Team slug_field = "tla" template_name_suffix = "_detail_about" @@ -43,8 +42,8 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: score_info = srcomp.get_score_info_for_team(self.object.tla) return super().get_context_data(score_info=score_info, **kwargs) -class TeamDetailCommentsView(LoginRequiredMixin, DetailView): +class TeamDetailCommentsView(LoginRequiredMixin, DetailView): model = Team slug_field = "tla" template_name_suffix = "_detail_comments" @@ -55,21 +54,21 @@ def get_context_data(self, **kwargs: dict[str, Any]) -> dict[str, Any]: **kwargs, ) -class TeamSubmitCommentFormView(LoginRequiredMixin, FormMixin, SingleObjectMixin, ProcessFormView): - http_method_names = ['post', 'put'] +class TeamSubmitCommentFormView(LoginRequiredMixin, FormMixin, SingleObjectMixin, ProcessFormView): + http_method_names = ["post", "put"] model = Team slug_field = "tla" form_class = CommentSubmitForm def get_success_url(self) -> str: - return reverse_lazy('teams:team_detail_comments', kwargs={"slug": self.get_object().tla}) + return reverse_lazy("teams:team_detail_comments", kwargs={"slug": self.get_object().tla}) def form_valid(self, form: CommentSubmitForm) -> HttpResponse: assert self.request.user.is_authenticated team = self.get_object() team.comments.create( - content=form.cleaned_data['comment'], + content=form.cleaned_data["comment"], author=self.request.user, ) return HttpResponseRedirect(redirect_to=self.get_success_url()) @@ -107,7 +106,6 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: class TeamDetailTimelineView(LoginRequiredMixin, DetailView): - model = Team slug_field = "tla" template_name_suffix = "_detail_timeline" @@ -115,37 +113,54 @@ class TeamDetailTimelineView(LoginRequiredMixin, DetailView): def get_entries(self) -> QuerySet[Any]: fields = ("entry_type", "entry_timestamp", "entry_user", "entry_content", "entry_style_info") - ticket_opens = TicketEvent.objects.filter(ticket__team=self.object, new_status__exact="OP").annotate( - entry_type=Value('Ticket Opened ', output_field=CharField()), - entry_timestamp=F("created_at"), - entry_user=F("user"), - entry_content=F("comment"), - entry_style_info=Value('is-info', output_field=CharField()), - ).values(*fields) - - ticket_resolves = TicketEvent.objects.filter(ticket__team=self.object, new_status__exact="RS").annotate( - entry_type=Value('Ticket Resolved ', output_field=CharField()), - entry_timestamp=F("created_at"), - entry_user=F("user"), - entry_content=F("comment"), - entry_style_info=Value('is-success', output_field=CharField()), - ).values(*fields) - - ticket_comments = TicketEvent.objects.filter(ticket__team=self.object, new_status__exact="").annotate( - entry_type=Value('Ticket Comment ', output_field=CharField()), - entry_timestamp=F("created_at"), - entry_user=F("user"), - entry_content=F("comment"), - entry_style_info=Value('', output_field=CharField()), - ).values(*fields) - - team_comments = TeamComment.objects.filter(team=self.object).order_by().annotate( - entry_type=Value('Team Comment', output_field=CharField()), - entry_timestamp=F("created_at"), - entry_user=F("author"), - entry_content=F("content"), - entry_style_info=Value('', output_field=CharField()), - ).values(*fields) + ticket_opens = ( + TicketEvent.objects.filter(ticket__team=self.object, new_status__exact="OP") + .annotate( + entry_type=Value("Ticket Opened ", output_field=CharField()), + entry_timestamp=F("created_at"), + entry_user=F("user"), + entry_content=F("comment"), + entry_style_info=Value("is-info", output_field=CharField()), + ) + .values(*fields) + ) + + ticket_resolves = ( + TicketEvent.objects.filter(ticket__team=self.object, new_status__exact="RS") + .annotate( + entry_type=Value("Ticket Resolved ", output_field=CharField()), + entry_timestamp=F("created_at"), + entry_user=F("user"), + entry_content=F("comment"), + entry_style_info=Value("is-success", output_field=CharField()), + ) + .values(*fields) + ) + + ticket_comments = ( + TicketEvent.objects.filter(ticket__team=self.object, new_status__exact="") + .annotate( + entry_type=Value("Ticket Comment ", output_field=CharField()), + entry_timestamp=F("created_at"), + entry_user=F("user"), + entry_content=F("comment"), + entry_style_info=Value("", output_field=CharField()), + ) + .values(*fields) + ) + + team_comments = ( + TeamComment.objects.filter(team=self.object) + .order_by() + .annotate( + entry_type=Value("Team Comment", output_field=CharField()), + entry_timestamp=F("created_at"), + entry_user=F("author"), + entry_content=F("content"), + entry_style_info=Value("", output_field=CharField()), + ) + .values(*fields) + ) return ticket_comments.union(ticket_opens, ticket_resolves, team_comments).order_by("-entry_timestamp") diff --git a/helpdesk/templates/account/login.html b/helpdesk/templates/account/login.html index b1579a3..e28128d 100644 --- a/helpdesk/templates/account/login.html +++ b/helpdesk/templates/account/login.html @@ -6,22 +6,26 @@ {% block title %}Log In{% endblock %} {% block content %} + {% get_providers as socialaccount_providers %} +

{{ SYSTEM_TITLE }}

{% include "inc/parts/messages.html" %} + {% if socialaccount_providers %} +
+ Log In With SR Google Account +
+ {% endif %}
{% csrf_token %} {{ form | crispy }}
- -
-
- Log In With SR Google Account +
Sign Up
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/helpdesk/templates/account/logout.html b/helpdesk/templates/account/logout.html index fbef188..18d2bc2 100644 --- a/helpdesk/templates/account/logout.html +++ b/helpdesk/templates/account/logout.html @@ -5,17 +5,17 @@ {% block head_title %}{% trans "Sign Out" %}{% endblock %} {% block content %} -

{% trans "Sign Out" %}

+

{% trans "Sign Out" %}

-

{% trans 'Are you sure you want to sign out?' %}

-
-
- {% csrf_token %} - {% if redirect_field_value %} - - {% endif %} - -
+

{% trans 'Are you sure you want to sign out?' %}

+
+
+ {% csrf_token %} + {% if redirect_field_value %} + + {% endif %} + +
{% endblock %} diff --git a/helpdesk/templates/account/signup_closed.html b/helpdesk/templates/account/signup_closed.html index 0ee24a3..bedefbd 100644 --- a/helpdesk/templates/account/signup_closed.html +++ b/helpdesk/templates/account/signup_closed.html @@ -6,6 +6,8 @@ {% block title %}Log In{% endblock %} {% block content %} + {% get_providers as socialaccount_providers %} +
@@ -16,6 +18,8 @@

{{ SYSTEM_TITLE }}

Go to Sign Up
- Log In With SR Google Account + {% if socialaccount_providers %} + Log In With SR Google Account + {% endif %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/helpdesk/templates/accounts/onboarding.html b/helpdesk/templates/accounts/onboarding.html index 6dabf7c..7a30a3b 100644 --- a/helpdesk/templates/accounts/onboarding.html +++ b/helpdesk/templates/accounts/onboarding.html @@ -5,13 +5,13 @@ {% block title %}Welcome to {{SYSTEM_TITLE}}{% endblock %} {% block content %} -
-

Hi! 👋

-

Let's get started by filling out some details. You can always update your information later in your profile settings.

-
-
- {% csrf_token %} - {{ form|crispy }} - -
+
+

Hi! 👋

+

Let's get started by filling out some details. You can always update your information later in your profile settings.

+
+
+ {% csrf_token %} + {{ form|crispy }} + +
{% endblock %} diff --git a/helpdesk/templates/accounts/user_form.html b/helpdesk/templates/accounts/user_form.html index ecd5010..35da4fc 100644 --- a/helpdesk/templates/accounts/user_form.html +++ b/helpdesk/templates/accounts/user_form.html @@ -5,9 +5,9 @@ {% block title %}Update Profile{% endblock %} {% block content %} -
- {% csrf_token %} - {{ form|crispy }} - -
+
+ {% csrf_token %} + {{ form|crispy }} + +
{% endblock %} diff --git a/helpdesk/templates/display/helpdesk.html b/helpdesk/templates/display/helpdesk.html index cc154dd..a3bc0b2 100644 --- a/helpdesk/templates/display/helpdesk.html +++ b/helpdesk/templates/display/helpdesk.html @@ -1,54 +1,54 @@ {% extends "layouts/base.html" %} {% block body %} -
-
-
-
-
-

{{SYSTEM_TITLE}} Status

-
- -
- +
+
+
+
+
+

{{SYSTEM_TITLE}} Status

+
-
-
-
-
- + + + + {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/inc/modals/assign_ticket.html b/helpdesk/templates/inc/modals/assign_ticket.html index 7aef346..929cc34 100644 --- a/helpdesk/templates/inc/modals/assign_ticket.html +++ b/helpdesk/templates/inc/modals/assign_ticket.html @@ -1,23 +1,23 @@ {% load crispy_forms_tags %} \ No newline at end of file + \ No newline at end of file diff --git a/helpdesk/templates/inc/modals/filter.html b/helpdesk/templates/inc/modals/filter.html index 09a309f..e9ad669 100644 --- a/helpdesk/templates/inc/modals/filter.html +++ b/helpdesk/templates/inc/modals/filter.html @@ -1,22 +1,22 @@ {% load crispy_forms_tags %} \ No newline at end of file + \ No newline at end of file diff --git a/helpdesk/templates/inc/nav/nav.html b/helpdesk/templates/inc/nav/nav.html index 6123097..db6be3e 100644 --- a/helpdesk/templates/inc/nav/nav.html +++ b/helpdesk/templates/inc/nav/nav.html @@ -1,68 +1,68 @@ + + + diff --git a/helpdesk/templates/inc/nav/team-tabs.html b/helpdesk/templates/inc/nav/team-tabs.html index 3562898..a89b7c4 100644 --- a/helpdesk/templates/inc/nav/team-tabs.html +++ b/helpdesk/templates/inc/nav/team-tabs.html @@ -13,16 +13,16 @@
  • - - - Comments - - -
  • - - - Timeline - - - + + + Comments + + +
  • + + + Timeline + + + \ No newline at end of file diff --git a/helpdesk/templates/inc/parts/messages.html b/helpdesk/templates/inc/parts/messages.html index 0514a2a..8781062 100644 --- a/helpdesk/templates/inc/parts/messages.html +++ b/helpdesk/templates/inc/parts/messages.html @@ -1,8 +1,8 @@ {% if messages %} {% for message in messages %} -
    - - {{ message }} -
    +
    + + {{ message }} +
    {% endfor %} {% endif %} \ No newline at end of file diff --git a/helpdesk/templates/layouts/base.html b/helpdesk/templates/layouts/base.html index 48a4100..34dd2ca 100644 --- a/helpdesk/templates/layouts/base.html +++ b/helpdesk/templates/layouts/base.html @@ -1,96 +1,96 @@ - - - - {% block title %}{{ SYSTEM_TITLE }}{% endblock %} | {{ SYSTEM_TITLE }} - - - - - - {% block body %} - {% endblock %} - - + \ No newline at end of file diff --git a/helpdesk/templates/layouts/base_app.html b/helpdesk/templates/layouts/base_app.html index f05f602..db6e862 100644 --- a/helpdesk/templates/layouts/base_app.html +++ b/helpdesk/templates/layouts/base_app.html @@ -4,11 +4,11 @@ {% include "inc/nav/nav.html" %}
    -
    +
    -

    {% block page_title %}{% endblock %}

    +

    {% block page_title %}{% endblock %}

    -
    +
    {% block page_buttons %}{% endblock %}
    @@ -17,4 +17,4 @@

    {% block page_title %}{% endbloc {% endblock %}

    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/helpdesk/templates/layouts/base_hero.html b/helpdesk/templates/layouts/base_hero.html index 0d930c2..6f1eea5 100644 --- a/helpdesk/templates/layouts/base_hero.html +++ b/helpdesk/templates/layouts/base_hero.html @@ -1,7 +1,7 @@ {% extends "layouts/base.html" %} {% block body %} -
    +
    diff --git a/helpdesk/templates/search.html b/helpdesk/templates/search.html index 575e0f9..9c9af58 100644 --- a/helpdesk/templates/search.html +++ b/helpdesk/templates/search.html @@ -6,11 +6,11 @@ {% block title %}Search: {{ q }}{% endblock %} {% block content %} -
    - - - - -
    -{% render_table table %} +
    + + + + +
    + {% render_table table %} {% endblock %} diff --git a/helpdesk/templates/socialaccount/login.html b/helpdesk/templates/socialaccount/login.html index e050b84..8fe89e4 100644 --- a/helpdesk/templates/socialaccount/login.html +++ b/helpdesk/templates/socialaccount/login.html @@ -1,7 +1,6 @@ {% extends "layouts/base_hero.html" %} {% load crispy_forms_tags %} {% load i18n %} -{% load socialaccount %} {% block title %}Log In{% endblock %} @@ -11,7 +10,7 @@

    {{ SYSTEM_TITLE }}

    Sign In Via Google

    -

    Non-SR Google accounts will not work. If you do not have an SR Google Account, please go back and sign up.

    +

    Non-SR Google accounts will not work. If you do not have an SR Google Account, please go back and sign up.

    {% csrf_token %} {{ form | crispy }} diff --git a/helpdesk/templates/teams/team_detail_about.html b/helpdesk/templates/teams/team_detail_about.html index edb9380..be4818c 100644 --- a/helpdesk/templates/teams/team_detail_about.html +++ b/helpdesk/templates/teams/team_detail_about.html @@ -6,27 +6,27 @@ {% block title %}{{object.tla}}: {{ object.name }}{% endblock %} {% block page_buttons %} - - New Ticket for {{ object.tla }} - + + New Ticket for {{ object.tla }} + {% endblock %} {% block content %} -{% include "inc/nav/team-tabs.html" with active="about" team=object %} -
    -
    -

    Team Name: {{object.name}}

    -

    TLA: {{object.tla}}

    -

    Rookie: {{object.is_rookie}}

    -

    Pit Location: {{object.pit_location}}

    -
    + {% include "inc/nav/team-tabs.html" with active="about" team=object %} +
    +
    +

    Team Name: {{object.name}}

    +

    TLA: {{object.tla}}

    +

    Rookie: {{object.is_rookie}}

    +

    Pit Location: {{object.pit_location}}

    +
    - {% if score_info %} -
    -

    League Position: {{score_info.league_pos}}

    -

    League Points: {{score_info.league_score}}

    -

    Game Points: {{score_info.game_score}}

    + {% if score_info %} +
    +

    League Position: {{score_info.league_pos}}

    +

    League Points: {{score_info.league_score}}

    +

    Game Points: {{score_info.game_score}}

    +
    + {% endif %}
    - {% endif %} -
    {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/teams/team_detail_comments.html b/helpdesk/templates/teams/team_detail_comments.html index 798fec6..96a601b 100644 --- a/helpdesk/templates/teams/team_detail_comments.html +++ b/helpdesk/templates/teams/team_detail_comments.html @@ -6,32 +6,32 @@ {% block title %}{{object.tla}}: {{ object.name }}{% endblock %} {% block page_buttons %} - - New Ticket for {{ object.tla }} - + + New Ticket for {{ object.tla }} + {% endblock %} {% block content %} -{% include "inc/nav/team-tabs.html" with active="comments" team=object %} -{% for comment in object.comments.all %} -
    -
    -

    {{ comment.created_at }} - {{ comment.author }}

    -
    + {% include "inc/nav/team-tabs.html" with active="comments" team=object %} + {% for comment in object.comments.all %} +
    +
    +

    {{ comment.created_at }} - {{ comment.author }}

    +
    +
    + {{comment.content|linebreaks}} +
    +
    + {% endfor %} +
    - {{comment.content|linebreaks}} + + {% csrf_token %} + {{ comment_form | crispy }} +
    + +
    +
    -{% endfor %} -
    -
    -
    - {% csrf_token %} - {{ comment_form | crispy }} -
    - -
    -
    -
    -
    {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/teams/team_detail_tickets.html b/helpdesk/templates/teams/team_detail_tickets.html index f756934..9cc27b9 100644 --- a/helpdesk/templates/teams/team_detail_tickets.html +++ b/helpdesk/templates/teams/team_detail_tickets.html @@ -6,18 +6,18 @@ {% block title %}{{object.tla}}: {{ object.name }}{% endblock %} {% block page_buttons %} - - - New Ticket for {{ object.tla }} - + + + New Ticket for {{ object.tla }} + {% endblock %} {% block content %} -{% include "inc/nav/team-tabs.html" with active="tickets" team=object %} -
    -
    - {% render_table table %} + {% include "inc/nav/team-tabs.html" with active="tickets" team=object %} +
    +
    + {% render_table table %} +
    -
    -{% include "inc/modals/filter.html" %} + {% include "inc/modals/filter.html" %} {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/teams/team_detail_timeline.html b/helpdesk/templates/teams/team_detail_timeline.html index c1b36ac..efb4ea4 100644 --- a/helpdesk/templates/teams/team_detail_timeline.html +++ b/helpdesk/templates/teams/team_detail_timeline.html @@ -6,26 +6,26 @@ {% block title %}{{object.tla}}: {{ object.name }}{% endblock %} {% block page_buttons %} - - New Ticket for {{ object.tla }} - + + New Ticket for {{ object.tla }} + {% endblock %} {% block content %} -{% include "inc/nav/team-tabs.html" with active="timeline" team=object %} -
    -

    This page is experimental, full details can be found on individual tickets.

    - {% for entry in entries %} -
    -
    -

    {{entry.entry_type}} {{ entry.entry_timestamp }}

    -
    - {% if entry.entry_content %} -
    - {{entry.entry_content|linebreaks}} + {% include "inc/nav/team-tabs.html" with active="timeline" team=object %} +
    +

    This page is experimental, full details can be found on individual tickets.

    + {% for entry in entries %} +
    +
    +

    {{entry.entry_type}} {{ entry.entry_timestamp }}

    - {% endif %} -
    - {% endfor %} -
    + {% if entry.entry_content %} +
    + {{entry.entry_content|linebreaks}} +
    + {% endif %} +
    + {% endfor %} +
    {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/teams/team_filter.html b/helpdesk/templates/teams/team_filter.html index 1685130..e9b949c 100644 --- a/helpdesk/templates/teams/team_filter.html +++ b/helpdesk/templates/teams/team_filter.html @@ -6,14 +6,14 @@ {% block title %}Teams{% endblock %} {% block page_buttons %} - + {% endblock %} {% block content %} -
    -
    - {% render_table table %} +
    +
    + {% render_table table %} +
    -
    -{% include "inc/modals/filter.html" %} + {% include "inc/modals/filter.html" %} {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/tickets/ticket_detail.html b/helpdesk/templates/tickets/ticket_detail.html index 587296f..060a345 100644 --- a/helpdesk/templates/tickets/ticket_detail.html +++ b/helpdesk/templates/tickets/ticket_detail.html @@ -2,67 +2,67 @@ {% load crispy_forms_tags %} {% block page_title %} -{{ object.status_name }} -{{ object.title }} #{{object.id}} + {{ object.status_name }} + {{ object.title }} #{{object.id}} {% endblock %} {% block title %}{{ object.title }} #{{object.id}}{% endblock %} {% block page_buttons %} -{% if object.is_escalatable %} -
    - {% csrf_token %} - -
    -{% endif %} - -{% if object.assignee != user %} -
    - {% csrf_token %} - - -
    -{% endif %} - - Edit Details - + {% if object.is_escalatable %} +
    + {% csrf_token %} + +
    + {% endif %} + + {% if object.assignee != user %} +
    + {% csrf_token %} + + +
    + {% endif %} + + Edit Details + {% endblock %} {% block content %} -
    - -
    - {% for event in object.events.all %} - {% if event.new_status == "OP" %} -
    - {% elif event.new_status == "RS" %} -
    - {% else %} -
    - {% endif %} +
    + +
    + {% for event in object.events.all %} + {% if event.new_status == "OP" %} +
    + {% elif event.new_status == "RS" %} +
    + {% else %} +
    + {% endif %}

    {% if event.new_status == "OP" %} - Opened at {{ event.created_at }} by {{ event.user }} + Opened at {{ event.created_at }} by {{ event.user }} {% elif event.new_status == "RS" %} Resolved at {{ event.created_at }} by {{ event.user }} {% else %} @@ -70,9 +70,9 @@ {% endif %} {% if event.assignee_change %} {% if event.assignee_change.user %} - - Assigned to {{ event.assignee_change.user }} + - Assigned to {{ event.assignee_change.user }} {% else %} - - Unassigned + - Unassigned {% endif %} {% endif %} @@ -83,27 +83,27 @@ {{event.comment|linebreaks}} {% endif %}

    -
    - {% endfor %} +
    + {% endfor %} -
    -
    -
    - {% csrf_token %} - {{ comment_form | crispy }} -
    - - {% if object.status != "RS" %} - - {% endif %} - {% if object.status != "OP" %} - - {% endif %} -
    -
    -
    -
    +
    +
    +
    + {% csrf_token %} + {{ comment_form | crispy }} +
    + + {% if object.status != "RS" %} + + {% endif %} + {% if object.status != "OP" %} + + {% endif %} +
    +
    +
    +
    +
    -
    -{% include "inc/modals/assign_ticket.html" %} -{% endblock %} \ No newline at end of file + {% include "inc/modals/assign_ticket.html" %} +{% endblock %} diff --git a/helpdesk/templates/tickets/ticket_form.html b/helpdesk/templates/tickets/ticket_form.html index fdeb0b4..a233f20 100644 --- a/helpdesk/templates/tickets/ticket_form.html +++ b/helpdesk/templates/tickets/ticket_form.html @@ -17,14 +17,14 @@ {% endblock %} {% block content %} -{% if create %} -

    Most tickets should be assigned after they are created so that teams do not skip the queue. Please do not assign tickets to a volunteer unless you know that there are no other teams waiting in the queue.

    -{% else %} -

    If you need to edit something that cannot be edited here, contact an administrator or create a new ticket.

    -{% endif %} -
    - {% csrf_token %} - {{form|crispy}} - -
    + {% if create %} +

    Most tickets should be assigned after they are created so that teams do not skip the queue. Please do not assign tickets to a volunteer unless you know that there are no other teams waiting in the queue.

    + {% else %} +

    If you need to edit something that cannot be edited here, contact an administrator or create a new ticket.

    + {% endif %} +
    + {% csrf_token %} + {{form|crispy}} + +
    {% endblock %} diff --git a/helpdesk/templates/tickets/ticketqueue_assigned.html b/helpdesk/templates/tickets/ticketqueue_assigned.html index f55c5e3..0a0522b 100644 --- a/helpdesk/templates/tickets/ticketqueue_assigned.html +++ b/helpdesk/templates/tickets/ticketqueue_assigned.html @@ -5,13 +5,13 @@ {% block title %}My Tickets{% endblock %} {% block page_buttons %} - + {% endblock %} {% block content %} -
    -
    - {% render_table table %} -
    -{% include "inc/modals/filter.html" %} +
    +
    + {% render_table table %} +
    + {% include "inc/modals/filter.html" %} {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/tickets/ticketqueue_detail.html b/helpdesk/templates/tickets/ticketqueue_detail.html index af20205..dcd89ff 100644 --- a/helpdesk/templates/tickets/ticketqueue_detail.html +++ b/helpdesk/templates/tickets/ticketqueue_detail.html @@ -5,43 +5,43 @@ {% block title %}Queue: {{ object.name }}{% endblock %} {% block page_buttons %} - - + + New Ticket - + {% endblock %} {% block content %} -
    -
    -
    +
    +
    +
    -
    -
    -

    {{ object.description }}

    - {% render_table table %} +
    +
    +

    {{ object.description }}

    + {% render_table table %} +
    -
    -{% include "inc/modals/filter.html" %} + {% include "inc/modals/filter.html" %} {% endblock %} \ No newline at end of file diff --git a/helpdesk/templates/tickets/tickets_all.html b/helpdesk/templates/tickets/tickets_all.html index 0c5865d..b769558 100644 --- a/helpdesk/templates/tickets/tickets_all.html +++ b/helpdesk/templates/tickets/tickets_all.html @@ -5,16 +5,16 @@ {% block title %}All Tickets{% endblock %} {% block page_buttons %} - - - New Ticket - + + + New Ticket + {% endblock %} {% block content %} -
    -
    - {% render_table table %} -
    -{% include "inc/modals/filter.html" %} +
    +
    + {% render_table table %} +
    + {% include "inc/modals/filter.html" %} {% endblock %} \ No newline at end of file diff --git a/helpdesk/tickets/admin.py b/helpdesk/tickets/admin.py index 1998ad5..2228b73 100644 --- a/helpdesk/tickets/admin.py +++ b/helpdesk/tickets/admin.py @@ -11,13 +11,14 @@ class TicketEventAdmin(admin.StackedInline): model = TicketEvent extra = 1 - readonly_fields = ('created_at', ) + readonly_fields = ("created_at",) + class TicketAdmin(admin.ModelAdmin): list_display = ("id", "title", "team", "queue") list_filter = ("queue", "team") - inlines = (TicketEventAdmin, ) + inlines = (TicketEventAdmin,) can_delete = False diff --git a/helpdesk/tickets/filters.py b/helpdesk/tickets/filters.py index 370a2e4..6074c05 100644 --- a/helpdesk/tickets/filters.py +++ b/helpdesk/tickets/filters.py @@ -10,12 +10,11 @@ class TicketFilter(django_filters.FilterSet): - def __init__(self, *args: Any, **kwargs: Any) -> None: - initial_status = kwargs.pop("initial_status", None) - super().__init__(*args, **kwargs) - if initial_status is not None: - self.form.initial["status"] = initial_status + initial_status = kwargs.pop("initial_status", None) + super().__init__(*args, **kwargs) + if initial_status is not None: + self.form.initial["status"] = initial_status status = django_filters.ChoiceFilter(label="Status", choices=TicketStatus.choices) # assignee_id = django_filters.ChoiceFilter( @@ -23,10 +22,14 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # choices=[(user.id, user.get_full_name()) for user in User.objects.all()], # ) queue = django_filters.ModelChoiceFilter( - queryset=TicketQueue.objects.all(), field_name="queue", to_field_name="slug", + queryset=TicketQueue.objects.all(), + field_name="queue", + to_field_name="slug", ) team = django_filters.ModelChoiceFilter( - queryset=Team.objects.all(), field_name="team", to_field_name="tla", + queryset=Team.objects.all(), + field_name="team", + to_field_name="tla", ) class Meta: diff --git a/helpdesk/tickets/forms.py b/helpdesk/tickets/forms.py index 390cfe0..f7b3544 100644 --- a/helpdesk/tickets/forms.py +++ b/helpdesk/tickets/forms.py @@ -6,7 +6,6 @@ class TicketCreationForm(forms.ModelForm): - description = forms.CharField(widget=forms.Textarea(attrs={"rows": "5"})) # Exclude queues that are escalated. @@ -14,10 +13,9 @@ class TicketCreationForm(forms.ModelForm): class Meta: model = Ticket - fields = ('title', 'queue', 'team') + fields = ("title", "queue", "team") class TicketAssignForm(forms.Form): - user = forms.ModelChoiceField(User.objects.all(), to_field_name="username", required=False) comment = forms.CharField(label="Optional Comment", widget=forms.Textarea(attrs={"rows": "3"}), required=False) diff --git a/helpdesk/tickets/migrations/0001_create_ticket_models.py b/helpdesk/tickets/migrations/0001_create_ticket_models.py index 0ae30f2..4105b3c 100644 --- a/helpdesk/tickets/migrations/0001_create_ticket_models.py +++ b/helpdesk/tickets/migrations/0001_create_ticket_models.py @@ -91,7 +91,8 @@ class Migration(migrations.Migration): ( "resolved_at", models.DateTimeField( - auto_now_add=True, verbose_name="Resolution Time", + auto_now_add=True, + verbose_name="Resolution Time", ), ), ( diff --git a/helpdesk/tickets/migrations/0002_add_queue_display_priority_field.py b/helpdesk/tickets/migrations/0002_add_queue_display_priority_field.py index e40c506..fe76cbd 100644 --- a/helpdesk/tickets/migrations/0002_add_queue_display_priority_field.py +++ b/helpdesk/tickets/migrations/0002_add_queue_display_priority_field.py @@ -4,19 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('tickets', '0001_create_ticket_models'), + ("tickets", "0001_create_ticket_models"), ] operations = [ migrations.AlterModelOptions( - name='ticketqueue', - options={'ordering': ['-display_priority', 'name']}, + name="ticketqueue", + options={"ordering": ["-display_priority", "name"]}, ), migrations.AddField( - model_name='ticketqueue', - name='display_priority', - field=models.PositiveSmallIntegerField(default=1, verbose_name='Display Priority'), + model_name="ticketqueue", + name="display_priority", + field=models.PositiveSmallIntegerField(default=1, verbose_name="Display Priority"), ), ] diff --git a/helpdesk/tickets/migrations/0003_add_escalation_field.py b/helpdesk/tickets/migrations/0003_add_escalation_field.py index b6fe148..30a79f5 100644 --- a/helpdesk/tickets/migrations/0003_add_escalation_field.py +++ b/helpdesk/tickets/migrations/0003_add_escalation_field.py @@ -5,22 +5,21 @@ class Migration(migrations.Migration): - dependencies = [ - ('tickets', '0002_add_queue_display_priority_field'), + ("tickets", "0002_add_queue_display_priority_field"), ] operations = [ migrations.AddField( - model_name='ticketqueue', - name='escalation_queue', + model_name="ticketqueue", + name="escalation_queue", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='escalates_to', - related_query_name='escalates_to', - to='tickets.ticketqueue', + related_name="escalates_to", + related_query_name="escalates_to", + to="tickets.ticketqueue", ), ), ] diff --git a/helpdesk/tickets/migrations/0004_add_missing_unique_constraint.py b/helpdesk/tickets/migrations/0004_add_missing_unique_constraint.py index b4bc59d..8f653e4 100644 --- a/helpdesk/tickets/migrations/0004_add_missing_unique_constraint.py +++ b/helpdesk/tickets/migrations/0004_add_missing_unique_constraint.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('tickets', '0003_add_escalation_field'), + ("tickets", "0003_add_escalation_field"), ] operations = [ migrations.AlterField( - model_name='ticketqueue', - name='slug', - field=models.SlugField(max_length=32, unique=True, verbose_name='Slug'), + model_name="ticketqueue", + name="slug", + field=models.SlugField(max_length=32, unique=True, verbose_name="Slug"), ), ] diff --git a/helpdesk/tickets/migrations/0005_move_to_ticket_events.py b/helpdesk/tickets/migrations/0005_move_to_ticket_events.py index e81a370..e42ceab 100644 --- a/helpdesk/tickets/migrations/0005_move_to_ticket_events.py +++ b/helpdesk/tickets/migrations/0005_move_to_ticket_events.py @@ -46,7 +46,8 @@ def create_events(apps: StateApps, schema_editor: BaseDatabaseSchemaEditor) -> N def create_old_objects( - apps: StateApps, schema_editor: BaseDatabaseSchemaEditor, + apps: StateApps, + schema_editor: BaseDatabaseSchemaEditor, ) -> None: Ticket = apps.get_model("tickets", "Ticket") # noqa: N806 TicketComment = apps.get_model("tickets", "TicketComment") # noqa: N806 @@ -149,38 +150,38 @@ class Migration(migrations.Migration): ), migrations.RunPython(create_events, create_old_objects), migrations.AlterField( - model_name='ticketevent', - name='created_at', + model_name="ticketevent", + name="created_at", field=models.DateTimeField(auto_now_add=True), ), migrations.RemoveField( - model_name='ticketresolution', - name='ticket', + model_name="ticketresolution", + name="ticket", ), migrations.RemoveField( - model_name='ticketresolution', - name='user', + model_name="ticketresolution", + name="user", ), migrations.RemoveField( - model_name='ticketcomment', - name='author', + model_name="ticketcomment", + name="author", ), migrations.RemoveField( - model_name='ticketcomment', - name='ticket', + model_name="ticketcomment", + name="ticket", ), migrations.DeleteModel( - name='TicketResolution', + name="TicketResolution", ), migrations.DeleteModel( - name='TicketComment', + name="TicketComment", ), migrations.RemoveField( - model_name='ticket', - name='description', + model_name="ticket", + name="description", ), migrations.RemoveField( - model_name='ticket', - name='opened_by', + model_name="ticket", + name="opened_by", ), ] diff --git a/helpdesk/tickets/migrations/0006_assignee_change_events.py b/helpdesk/tickets/migrations/0006_assignee_change_events.py index 9d467d1..a18cb5b 100644 --- a/helpdesk/tickets/migrations/0006_assignee_change_events.py +++ b/helpdesk/tickets/migrations/0006_assignee_change_events.py @@ -65,7 +65,7 @@ class Migration(migrations.Migration): ), ), migrations.RemoveField( - model_name='ticket', - name='assignee', + model_name="ticket", + name="assignee", ), ] diff --git a/helpdesk/tickets/migrations/0007_ticketqueue_description.py b/helpdesk/tickets/migrations/0007_ticketqueue_description.py index 04a87a7..c54981b 100644 --- a/helpdesk/tickets/migrations/0007_ticketqueue_description.py +++ b/helpdesk/tickets/migrations/0007_ticketqueue_description.py @@ -4,16 +4,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('tickets', '0006_assignee_change_events'), + ("tickets", "0006_assignee_change_events"), ] operations = [ migrations.AddField( - model_name='ticketqueue', - name='description', - field=models.CharField(default='', max_length=100, verbose_name='Description'), + model_name="ticketqueue", + name="description", + field=models.CharField(default="", max_length=100, verbose_name="Description"), preserve_default=False, ), ] diff --git a/helpdesk/tickets/migrations/0008_ticketqueue_show_in_overview.py b/helpdesk/tickets/migrations/0008_ticketqueue_show_in_overview.py index 4534350..290d1dd 100644 --- a/helpdesk/tickets/migrations/0008_ticketqueue_show_in_overview.py +++ b/helpdesk/tickets/migrations/0008_ticketqueue_show_in_overview.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('tickets', '0007_ticketqueue_description'), + ("tickets", "0007_ticketqueue_description"), ] operations = [ migrations.AddField( - model_name='ticketqueue', - name='show_in_overview', - field=models.BooleanField(default=True, verbose_name='Show in Display Overview'), + model_name="ticketqueue", + name="show_in_overview", + field=models.BooleanField(default=True, verbose_name="Show in Display Overview"), ), ] diff --git a/helpdesk/tickets/models.py b/helpdesk/tickets/models.py index c0736c4..538e231 100644 --- a/helpdesk/tickets/models.py +++ b/helpdesk/tickets/models.py @@ -17,12 +17,12 @@ class TicketQueue(models.Model): display_priority = models.PositiveSmallIntegerField("Display Priority", default=1) show_in_overview = models.BooleanField("Show in Display Overview", default=True) escalation_queue = models.ForeignKey( - 'tickets.TicketQueue', + "tickets.TicketQueue", on_delete=models.SET_NULL, null=True, blank=True, - related_name='escalates_to', - related_query_name='escalates_to', + related_name="escalates_to", + related_query_name="escalates_to", ) class Meta: @@ -46,21 +46,29 @@ def in_progress_count(self) -> int: ) return tickets.count() -class TicketQuerySet(models.QuerySet['Ticket']): +class TicketQuerySet(models.QuerySet["Ticket"]): def with_status(self) -> TicketQuerySet: - events = TicketEvent.objects.exclude(new_status__exact="").filter( - ticket_id=models.OuterRef("pk"), - ).order_by("-created_at") + events = ( + TicketEvent.objects.exclude(new_status__exact="") + .filter( + ticket_id=models.OuterRef("pk"), + ) + .order_by("-created_at") + ) return self.annotate(status=models.Subquery(events.values("new_status")[:1])) def with_assignee(self) -> TicketQuerySet: - events = TicketEvent.objects.annotate( - assignee=models.F("assignee_change__user"), - ).filter( - ticket_id=models.OuterRef("pk"), - assignee_change__isnull=False, - ).order_by("-created_at") + events = ( + TicketEvent.objects.annotate( + assignee=models.F("assignee_change__user"), + ) + .filter( + ticket_id=models.OuterRef("pk"), + assignee_change__isnull=False, + ) + .order_by("-created_at") + ) return self.annotate(assignee_id=models.Subquery(events.values("assignee")[:1])) def with_event_fields(self) -> TicketQuerySet: @@ -68,7 +76,6 @@ def with_event_fields(self) -> TicketQuerySet: class TicketManager(models.Manager): - def get_queryset(self) -> TicketQuerySet: # type: ignore[override] return TicketQuerySet(self.model, using=self._db) @@ -103,7 +110,7 @@ def __str__(self) -> str: return f"#{self.id} - {self.title}" def get_absolute_url(self) -> str: - return reverse_lazy('tickets:ticket_detail', kwargs={'pk': self.id}) + return reverse_lazy("tickets:ticket_detail", kwargs={"pk": self.id}) @property def assignee(self) -> User | None: @@ -160,6 +167,7 @@ class TicketEventAssigneeChange(models.Model): One instance of this model exists per user. """ + user = models.OneToOneField( "accounts.User", on_delete=models.PROTECT, @@ -205,9 +213,9 @@ class Meta: constraints = [ models.CheckConstraint( check=models.Q( - ~models.Q(new_status__exact="") | - ~models.Q(comment__exact="") | - models.Q(assignee_change__isnull=False), + ~models.Q(new_status__exact="") + | ~models.Q(comment__exact="") + | models.Q(assignee_change__isnull=False), ), name="valid_event", ), diff --git a/helpdesk/tickets/tables.py b/helpdesk/tickets/tables.py index c34e6d7..d9002df 100644 --- a/helpdesk/tickets/tables.py +++ b/helpdesk/tickets/tables.py @@ -4,26 +4,25 @@ class TicketTable(tables.Table): - id = tables.TemplateColumn("#{{record.id}}") # noqa: A003 - title = tables.LinkColumn('tickets:ticket_detail', args=[tables.A('id')]) + title = tables.LinkColumn("tickets:ticket_detail", args=[tables.A("id")]) team = tables.LinkColumn( - 'teams:team_detail', - args=[tables.A('team__tla')], - accessor=tables.A('team__tla'), + "teams:team_detail", + args=[tables.A("team__tla")], + accessor=tables.A("team__tla"), verbose_name="Team", ) status = tables.Column() assignee_id = tables.TemplateColumn( verbose_name="Assignee", - template_code="{{record.assignee|default:\"-\"}}", + template_code='{{record.assignee|default:"-"}}', ) - actions = tables.LinkColumn('tickets:ticket_detail', args=[tables.A('id')], text="View") + actions = tables.LinkColumn("tickets:ticket_detail", args=[tables.A("id")], text="View") class Meta: model = Ticket fields: list[str] = [] - order_by = 'created_at' + order_by = "created_at" def render_status(self, value: str) -> str: lookups = dict(TicketStatus.choices) diff --git a/helpdesk/tickets/urls.py b/helpdesk/tickets/urls.py index 11fb0df..201e0bd 100644 --- a/helpdesk/tickets/urls.py +++ b/helpdesk/tickets/urls.py @@ -21,7 +21,9 @@ urlpatterns = [ path("", RedirectToDefaultTicketQueue.as_view(), name="queue_default"), path( - "assigned-to-me", AssignedTicketListView.as_view(), name="ticket_assigned_list", + "assigned-to-me", + AssignedTicketListView.as_view(), + name="ticket_assigned_list", ), path("new", TicketCreateView.as_view(), name="ticket_create"), path("all", TicketListView.as_view(), name="ticket_all"), diff --git a/helpdesk/tickets/views.py b/helpdesk/tickets/views.py index 96da928..5291a19 100644 --- a/helpdesk/tickets/views.py +++ b/helpdesk/tickets/views.py @@ -114,8 +114,8 @@ def get_initial(self) -> dict[str, Any]: } def form_valid(self, form: forms.Form) -> HttpResponse: - resp = super().form_valid(form) - ticket = form.instance # type: ignore[attr-defined] + resp = super().form_valid(form) + ticket = form.instance # type: ignore[attr-defined] ticket.events.create( new_status=TicketStatus.OPEN, user=self.request.user, @@ -123,18 +123,19 @@ def form_valid(self, form: forms.Form) -> HttpResponse: ) return resp + class TicketUpdateView(LoginRequiredMixin, UpdateView): model = Ticket - fields = ('title', 'queue', 'team') + fields = ("title", "queue", "team") -class TicketEscalateFormView(LoginRequiredMixin, FormMixin, SingleObjectMixin, ProcessFormView): - http_method_names = ['post', 'put'] +class TicketEscalateFormView(LoginRequiredMixin, FormMixin, SingleObjectMixin, ProcessFormView): + http_method_names = ["post", "put"] model = Ticket form_class = Form def get_success_url(self) -> str: - return reverse_lazy('tickets:ticket_detail', kwargs={"pk": self.get_object().id}) + return reverse_lazy("tickets:ticket_detail", kwargs={"pk": self.get_object().id}) def form_valid(self, form: Form) -> HttpResponse: assert self.request.user.is_authenticated @@ -156,8 +157,7 @@ def form_invalid(self, form: Form) -> HttpResponse: class TicketAssignToUserFormView(LoginRequiredMixin, FormMixin, SingleObjectMixin, ProcessFormView): - - http_method_names = ['post', 'put'] + http_method_names = ["post", "put"] model = Ticket form_class = TicketAssignForm @@ -165,7 +165,7 @@ def get_queryset(self) -> QuerySet[Ticket]: return Ticket.objects.with_event_fields().all() def get_success_url(self) -> str: - return reverse_lazy('tickets:ticket_detail', kwargs={"pk": self.get_object().id}) + return reverse_lazy("tickets:ticket_detail", kwargs={"pk": self.get_object().id}) def form_valid(self, form: Form) -> HttpResponse: assert self.request.user.is_authenticated @@ -187,8 +187,7 @@ def form_invalid(self, form: Form) -> HttpResponse: class TicketSubmitCommentFormView(LoginRequiredMixin, FormMixin, SingleObjectMixin, ProcessFormView): - - http_method_names = ['post', 'put'] + http_method_names = ["post", "put"] model = Ticket form_class = CommentSubmitForm new_status = "" @@ -197,14 +196,14 @@ def get_queryset(self) -> QuerySet[Ticket]: return super().get_queryset().with_event_fields() # type: ignore[attr-defined] def get_success_url(self) -> str: - return reverse_lazy('tickets:ticket_detail', kwargs={"pk": self.get_object().id}) + return reverse_lazy("tickets:ticket_detail", kwargs={"pk": self.get_object().id}) def form_valid(self, form: CommentSubmitForm) -> HttpResponse: assert self.request.user.is_authenticated ticket = self.get_object() ticket.events.create( new_status=self.new_status, - comment=form.cleaned_data['comment'], + comment=form.cleaned_data["comment"], user=self.request.user, ) return HttpResponseRedirect(redirect_to=self.get_success_url()) @@ -214,7 +213,6 @@ def form_invalid(self, form: CommentSubmitForm) -> HttpResponse: class TicketResolveFormView(TicketSubmitCommentFormView): - new_status = TicketStatus.RESOLVED def form_valid(self, form: CommentSubmitForm) -> HttpResponse: @@ -228,7 +226,7 @@ def form_valid(self, form: CommentSubmitForm) -> HttpResponse: ticket.events.create( new_status=self.new_status, - comment=form.cleaned_data['comment'], + comment=form.cleaned_data["comment"], user=self.request.user, assignee_change=assignee_change, ) @@ -236,5 +234,4 @@ def form_valid(self, form: CommentSubmitForm) -> HttpResponse: class TicketReOpenFormView(TicketSubmitCommentFormView): - new_status = TicketStatus.OPEN diff --git a/requirements-dev.in b/requirements-dev.in index bc028f5..8d90bbb 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -1,6 +1,7 @@ pip-tools -r requirements.txt +djhtml ruff isort pytest diff --git a/requirements-dev.txt b/requirements-dev.txt index d3dc082..1b36b7b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,33 +4,33 @@ # # pip-compile requirements-dev.in # -asgiref==3.6.0 +asgiref==3.8.1 # via # -r requirements.txt # django -build==1.0.3 +build==1.2.1 # via pip-tools -cachetools==5.3.0 +cachetools==5.3.3 # via -r requirements.txt -certifi==2023.7.22 +certifi==2024.2.2 # via # -r requirements.txt # requests -cffi==1.15.1 +cffi==1.16.0 # via # -r requirements.txt # cryptography -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via # -r requirements.txt # requests click==8.1.7 # via pip-tools -coverage[toml]==7.3.1 +coverage[toml]==7.4.4 # via pytest-cov -crispy-bulma==0.8.3 +crispy-bulma==0.11.0 # via -r requirements.txt -cryptography==41.0.3 +cryptography==42.0.5 # via # -r requirements.txt # pyjwt @@ -38,97 +38,101 @@ defusedxml==0.7.1 # via # -r requirements.txt # python3-openid -django==4.2.7 +django==4.2.11 # via # -r requirements.txt # crispy-bulma # django-allauth + # django-crispy-forms # django-filter # django-stubs # django-stubs-ext # django-tables2 # django-tables2-bulma-template -django-allauth==0.53.0 +django-allauth==0.61.1 # via -r requirements.txt -django-crispy-forms==1.14.0 +django-crispy-forms==2.1 # via # -r requirements.txt # crispy-bulma -django-filter==22.1 +django-filter==24.2 # via -r requirements.txt django-filter-stubs==0.1.3 # via -r requirements-dev.in -django-stubs[compatible-mypy]==4.2.4 +django-stubs[compatible-mypy]==4.2.7 # via # -r requirements-dev.in # django-filter-stubs # djangorestframework-stubs -django-stubs-ext==4.2.2 +django-stubs-ext==4.2.7 # via # -r requirements.txt # django-stubs -django-tables2==2.5.2 +django-tables2==2.7.0 # via # -r requirements.txt # django-tables2-bulma-template django-tables2-bulma-template==0.2.0 # via -r requirements.txt -djangorestframework-stubs==3.14.2 +djangorestframework-stubs==3.14.5 # via django-filter-stubs -exceptiongroup==1.1.3 +djhtml==3.0.6 + # via -r requirements-dev.in +exceptiongroup==1.2.0 # via pytest -faker==19.6.1 +faker==24.7.1 # via -r requirements-dev.in -freezegun==1.2.2 +freezegun==1.4.0 # via -r requirements-dev.in -idna==3.4 +idna==3.6 # via # -r requirements.txt # requests iniconfig==2.0.0 # via pytest -isort==5.12.0 +isort==5.13.2 # via -r requirements-dev.in -mypy==1.5.1 +mypy==1.7.1 # via # -r requirements-dev.in # django-filter-stubs # django-stubs - # djangorestframework-stubs mypy-extensions==1.0.0 # via mypy oauthlib==3.2.2 # via # -r requirements.txt # requests-oauthlib -packaging==23.1 +packaging==24.0 # via # build # pytest -pip-tools==7.3.0 +pip-tools==7.4.1 # via -r requirements-dev.in -pluggy==1.3.0 +pluggy==1.4.0 # via pytest -pycparser==2.21 +pycparser==2.22 # via # -r requirements.txt # cffi -pyjwt[crypto]==2.6.0 +pyjwt[crypto]==2.8.0 # via # -r requirements.txt # django-allauth pyproject-hooks==1.0.0 - # via build -pytest==7.4.2 + # via + # build + # pip-tools +pytest==8.1.1 # via # -r requirements-dev.in # pytest-cov # pytest-django -pytest-cov==4.1.0 +pytest-cov==5.0.0 # via -r requirements-dev.in -pytest-django==4.5.2 +pytest-django==4.8.0 # via -r requirements-dev.in -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 # via # faker # freezegun @@ -142,11 +146,11 @@ requests==2.31.0 # django-allauth # djangorestframework-stubs # requests-oauthlib -requests-oauthlib==1.3.1 +requests-oauthlib==2.0.0 # via # -r requirements.txt # django-allauth -ruff==0.0.290 +ruff==0.3.5 # via -r requirements-dev.in six==1.16.0 # via python-dateutil @@ -163,35 +167,35 @@ tomli==2.0.1 # pip-tools # pyproject-hooks # pytest -types-cachetools==5.3.0.6 +types-cachetools==5.3.0.7 # via -r requirements-dev.in types-dj-database-url==1.3.0.4 # via -r requirements-dev.in -types-pytz==2023.3.0.1 +types-pytz==2024.1.0.20240203 # via django-stubs -types-pyyaml==6.0.12.11 +types-pyyaml==6.0.12.20240311 # via # django-stubs # djangorestframework-stubs -types-requests==2.31.0.2 +types-requests==2.31.0.20240403 # via djangorestframework-stubs -types-setuptools==68.2.0.0 +types-setuptools==69.2.0.20240317 # via -r requirements-dev.in -types-urllib3==1.26.25.14 - # via types-requests -typing-extensions==4.4.0 +typing-extensions==4.11.0 # via # -r requirements.txt + # asgiref # django-filter-stubs # django-stubs # django-stubs-ext # djangorestframework-stubs # mypy -urllib3==1.26.15 +urllib3==2.2.1 # via # -r requirements.txt # requests -wheel==0.41.2 + # types-requests +wheel==0.43.0 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements.txt b/requirements.txt index 5e6ad37..d770be0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,58 +1,61 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile requirements.in +# pip-compile --resolver=backtracking requirements.in # -asgiref==3.6.0 +asgiref==3.8.1 # via django -cachetools==5.3.0 +cachetools==5.3.3 # via -r requirements.in -certifi==2023.7.22 +certifi==2024.2.2 # via requests -cffi==1.15.1 +cffi==1.16.0 # via cryptography -charset-normalizer==3.1.0 +charset-normalizer==3.3.2 # via requests -crispy-bulma==0.8.3 +crispy-bulma==0.11.0 # via -r requirements.in -cryptography==41.0.3 +cryptography==42.0.5 # via pyjwt defusedxml==0.7.1 # via python3-openid -django==4.2.7 +django==4.2.11 # via # -r requirements.in # crispy-bulma # django-allauth + # django-crispy-forms # django-filter # django-stubs-ext # django-tables2 # django-tables2-bulma-template -django-allauth==0.53.0 +django-allauth==0.61.1 # via -r requirements.in -django-crispy-forms==1.14.0 +django-crispy-forms==2.1 # via # -r requirements.in # crispy-bulma -django-filter==22.1 +django-filter==24.2 # via -r requirements.in -django-stubs-ext==4.2.2 +django-stubs-ext==4.2.7 # via -r requirements.in -django-tables2==2.5.2 +django-tables2==2.7.0 # via # -r requirements.in # django-tables2-bulma-template django-tables2-bulma-template==0.2.0 # via -r requirements.in -idna==3.4 +idna==3.6 # via requests oauthlib==3.2.2 # via requests-oauthlib -pycparser==2.21 +pycparser==2.22 # via cffi -pyjwt[crypto]==2.6.0 - # via django-allauth +pyjwt[crypto]==2.8.0 + # via + # django-allauth + # pyjwt python3-openid==3.2.0 # via django-allauth requests==2.31.0 @@ -60,11 +63,11 @@ requests==2.31.0 # -r requirements.in # django-allauth # requests-oauthlib -requests-oauthlib==1.3.1 +requests-oauthlib==2.0.0 # via django-allauth sqlparse==0.4.4 # via django -typing-extensions==4.4.0 +typing-extensions==4.11.0 # via django-stubs-ext -urllib3==1.26.15 +urllib3==2.2.1 # via requests