2
0
Fork 0

Compare commits

...

14 Commits

Author SHA1 Message Date
Luca 595e0a47b9 fix(typing-extensions): incompatible version
continuous-integration/drone/push Build is passing Details
2024-05-04 00:38:00 +02:00
Luca 36d75dc50d fix(settings): env var spelling
continuous-integration/drone/push Build is passing Details
2024-05-04 00:27:28 +02:00
Luca 71fc7ed07e chore: ignore *.bkp files 2024-05-04 00:27:02 +02:00
Luca 468f68c2cb build: extend .dockerignore 2024-05-04 00:26:23 +02:00
Luca 3e75b76ac9 chore(Dockerfile*): update base image version 2024-05-04 00:16:25 +02:00
Luca adbb5685a0 feat(pages): populate navs from Page objects
continuous-integration/drone/push Build is failing Details
2024-05-04 00:06:58 +02:00
Luca 818e478353 feat: generate footer nav from context 2024-05-04 00:05:00 +02:00
Luca 0dd2c8f31a style: add .editorconfig
continuous-integration/drone/push Build is failing Details
2024-05-03 23:35:07 +02:00
Luca 59644b7b4b chore: update dependencies 2024-05-03 23:34:16 +02:00
Luca 54296ebcf7 refactor: move font, logo, style to core/static/
continuous-integration/drone/push Build is passing Details
2024-05-03 22:35:34 +02:00
Luca cfbc4ef61c feat: context processor for footer nav 2024-05-03 22:32:20 +02:00
Luca fdc74bbf9e ci: fix missing dependency for isort
continuous-integration/drone/push Build is passing Details
2024-05-03 22:10:13 +02:00
Luca 5ac11e16e8 ci: check code style
continuous-integration/drone/push Build is failing Details
2024-05-03 22:05:06 +02:00
Luca c3f2c0bcfa chore: remove librabbitmq as dependency 2024-05-03 22:02:50 +02:00
66 changed files with 305 additions and 147 deletions

View File

@ -1,10 +1,15 @@
**/__pycache__
*.bkp
.dockerignore
.drone.yml
.editorconfig
.git*
.idea
Dockerfile
Dockerfile-dev
README.md
db.sqlite3
docker-compose.yml
env
setup.cfg
storage

View File

@ -4,10 +4,15 @@ kind: pipeline
type: docker
name: default
clone:
disable: yes
steps:
- name: check style
image: python:3.12-alpine
commands:
- apk add --no-cache git # required by isort to skip files in .gitignore
- pip install black isort
- black --check .
- isort -c .
- name: deploy staging
image: ghcr.io/appleboy/drone-ssh
environment:
@ -26,6 +31,8 @@ steps:
when:
branch:
- main
event:
- push
- name: deploy production
image: ghcr.io/appleboy/drone-ssh
@ -35,7 +42,10 @@ steps:
when:
branch:
- live
event:
- push
trigger:
event:
- pull_request
- push

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
root = true
[*.html]
indent_size = 4
indent_style = space

3
.gitignore vendored
View File

@ -158,3 +158,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# E.g. copies of old dev database
*.bkp

View File

@ -1,6 +1,4 @@
FROM python:3.10-alpine3.17
RUN apk add --no-cache git
FROM python:3.12-alpine3.19
RUN adduser -h /opt/shiftregister -D shiftregister

View File

@ -1,6 +1,4 @@
FROM python:3.10-alpine3.17
RUN apk add --no-cache git
FROM python:3.12-alpine3.19
RUN adduser -h /home/shiftregister -D shiftregister

View File

@ -1,6 +1,6 @@
# shiftregister
![kontakt logo with its left half rotated 180 degrees, resembling the left shift operator](assets/tonkakt.svg)
![kontakt logo with its left half rotated 180 degrees, resembling the left shift operator](shiftregister/core/static/tonkakt.svg)
## [CI Mirror](https://git.luj0ga.de/kontakt/shiftregister) / [Drone CI](https://ci.luj0ga.de/kontakt/shiftregister)

View File

@ -1,9 +1,9 @@
amqp==5.1.1
asgiref==3.5.0
asgiref==3.8.1
async-timeout==4.0.2
beautifulsoup4==4.12.2
billiard==3.6.4.0
celery==5.2.6
beautifulsoup4==4.12.3
billiard==4.2.0
celery==5.4.0
certifi==2021.10.8
charset-normalizer==2.0.12
click==8.1.2
@ -11,31 +11,31 @@ click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
Deprecated==1.2.13
Django==4.0.4
django-dynamic-preferences==1.15.0
django-phonenumber-field==6.1.0
icalendar==4.0.9
Django==5.0.4
django-dynamic-preferences==1.16.0
django-phonenumber-field==7.3.0
icalendar==5.0.12
idna==3.3
kombu==5.2.4
librabbitmq==2.0.0
kombu==5.3.7
packaging==21.3
persisting-theory==1.0
phonenumbers==8.12.47
prompt-toolkit==3.0.29
psycopg2-binary==2.9.3
psycopg2-binary==2.9.9
pyparsing==3.0.8
pypng==0.20220715.0
python-dateutil==2.8.2
pytz==2022.1
qrcode==7.4.2
redis==4.2.2
requests==2.27.1
sentry-sdk==1.5.10
redis==5.0.4
requests==2.31.0
sentry-sdk==2.0.1
six==1.16.0
soupsieve==2.4.1
sqlparse==0.4.2
typing_extensions==4.5.0
urllib3==1.26.9
vine==5.0.0
typing_extensions==4.11.0
tzdata==2024.1
urllib3==2.2.1
vine==5.1.0
wcwidth==0.2.5
wrapt==1.14.0

4
setup.cfg Normal file
View File

@ -0,0 +1,4 @@
[isort]
line_length=88
profile=black
skip_gitignore=True

View File

@ -1,7 +1,7 @@
from django.contrib import admin
from django.contrib.admin import DateFieldListFilter
from .models import Room, Shift, Helper, ShiftRegistration, Message, LoginToken
from .models import Helper, LoginToken, Message, Room, Shift, ShiftRegistration
@admin.register(Room)

View File

@ -1,4 +1,5 @@
from django.conf import settings
from .models import LoginToken

View File

@ -1,8 +1,9 @@
from dynamic_preferences.registries import global_preferences_registry
from dynamic_preferences import types
import phonenumbers
import datetime
import phonenumbers
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
helper = types.Section("helper")

View File

@ -1,9 +1,10 @@
from django import forms
from .models import Helper
from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.validators import validate_international_phonenumber
from django.core.exceptions import ValidationError
from dynamic_preferences.registries import global_preferences_registry
from phonenumber_field.formfields import PhoneNumberField
from phonenumber_field.validators import validate_international_phonenumber
from .models import Helper
global_preferences = global_preferences_registry.manager()

View File

@ -1,8 +1,9 @@
# Generated by Django 4.0.4 on 2022-04-27 14:11
from django.db import migrations, models
import django.db.models.deletion
import phonenumber_field.modelfields
from django.db import migrations, models
import shiftregister.app.models

View File

@ -1,12 +1,13 @@
from django.db import models
import secrets
from django.shortcuts import reverse
from datetime import timedelta
from django.utils import timezone
from django.db.models import F, Count, Q, ExpressionWrapper, Case, When
from django.db import models
from django.db.models import Case, Count, ExpressionWrapper, F, Q, When
from django.shortcuts import reverse
from django.template import Context, Template
from phonenumber_field.modelfields import PhoneNumberField
from django.utils import timezone
from dynamic_preferences.registries import global_preferences_registry
from phonenumber_field.modelfields import PhoneNumberField
global_preferences = global_preferences_registry.manager()

View File

@ -2,7 +2,9 @@ from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.shortcuts import reverse
from django.template import Context, Template
from shiftregister.core.signals import populate_nav
from .models import Message, Shift

View File

@ -1,7 +1,7 @@
from datetime import timezone
from django.conf import settings
import requests
import requests
from django.conf import settings
BASE_URL = "https://api.sipgate.com/v2"

View File

@ -1,7 +1,6 @@
import requests
from django.conf import settings
from phonenumber_field.phonenumber import PhoneNumber
import requests
BASE_URL = "https://api.sipgate.com/v2"

View File

@ -1,11 +1,12 @@
import sentry_sdk
from celery import shared_task
from .models import Message, ShiftRegistration
from django.conf import settings
from django.db import transaction
from django.utils import timezone
from dynamic_preferences.registries import global_preferences_registry
from .models import Message, ShiftRegistration
from .sipgate.sms import send as send_sms
import sentry_sdk
global_preferences = global_preferences_registry.manager()

View File

@ -1,18 +1,20 @@
from django.shortcuts import render, redirect, get_object_or_404
from .models import Shift, LoginToken, Helper, ShiftRegistration
from django.db import transaction
from django.db.models import F, Count, Q, ExpressionWrapper
from django.core.cache import cache
from .forms import RegisterForm, EmptyForm, AstaForm
from django.db.models.fields import DateTimeField
import datetime
import math
from datetime import timedelta
from django.utils import timezone
from django.conf import settings
from django.contrib import messages
import datetime
from .decorators import event_state
from django.core.cache import cache
from django.db import transaction
from django.db.models import Count, ExpressionWrapper, F, Q
from django.db.models.fields import DateTimeField
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from dynamic_preferences.registries import global_preferences_registry
import math
from .decorators import event_state
from .forms import AstaForm, EmptyForm, RegisterForm
from .models import Helper, LoginToken, Shift, ShiftRegistration
global_preferences = global_preferences_registry.manager()

View File

@ -1,4 +1,4 @@
from .signals import populate_nav
from .signals import populate_footer_nav, populate_nav
def nav(request):
@ -9,6 +9,15 @@ def nav(request):
for item in items
]
return {
"nav_items": nav_items,
}
return {"nav_items": nav_items}
def footer_nav(request):
nav_items = [
item
for _, items in populate_footer_nav.send(sender=request)
if isinstance(items, list)
for item in items
]
return {"footer_nav_items": nav_items}

View File

@ -1,3 +1,5 @@
from django.dispatch import Signal
populate_nav = Signal()
populate_footer_nav = Signal()

View File

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 903 B

View File

@ -110,9 +110,13 @@
{% endblock %}
<div class="breadcrumb has-dot-separator is-flex-grow-1 is-right ml-5">
<ul>
<li><a href="{% url 'pages:view' 'map' %}">Karte</a></li>
<li><a href="{% url 'pages:view' 'faq' %}">Häufig gestellte Fragen</a></li>
<li><a href="{% url 'pages:view' 'about' %}">Über diese Seite/Impressum</a></li>
{% for item in footer_nav_items %}
<li>
<{% if item.link %}a{% else %}p{% endif %}{% if item.class %} class="{{ item.class }}"{% endif %}{% if item.link %} href="{{ item.link }}"{% endif %}>
{{ item.text }}
</{% if item.link %}a{% else %}p{% endif %}>
</li>
{% endfor %}
</ul>
</div>
</div>

View File

@ -1,5 +1,6 @@
from django.contrib import admin
from django.shortcuts import reverse
from .models import *

View File

@ -1,7 +1,7 @@
# Generated by Django 4.0.4 on 2023-05-07 16:00
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,6 +1,7 @@
# Generated by Django 4.0.4 on 2023-05-13 17:28
from django.db import migrations, models
import shiftregister.fallback.models

View File

@ -1,11 +1,12 @@
from shiftregister.importer.models import *
from django.db.models import Max, Sum
from django.db.models import Count, Exists, OuterRef, ExpressionWrapper
from django.db.models.lookups import LessThan
from django.db.models.fields import DateTimeField
from base64 import urlsafe_b64encode
import math
import secrets
from base64 import urlsafe_b64encode
from django.db.models import Count, Exists, ExpressionWrapper, Max, OuterRef, Sum
from django.db.models.fields import DateTimeField
from django.db.models.lookups import LessThan
from shiftregister.importer.models import *
night_shift_query = Q(start_at__hour__gte=21) | Q(start_at__hour__lte=10)

View File

@ -1,14 +1,15 @@
from datetime import datetime
from celery import shared_task
from .models import FallbackAssignment
from shiftregister.app.models import ShiftRegistration
from django.conf import settings
from django.db import transaction
from django.db.models import Case, Count, ExpressionWrapper, F, Q, When
from django.utils import timezone
from dynamic_preferences.registries import global_preferences_registry
from django.db.models import F, Count, Q, ExpressionWrapper, Case, When
from datetime import datetime
from django.utils import timezone
from shiftregister.app.models import ShiftRegistration
from .models import FallbackAssignment
global_preferences = global_preferences_registry.manager()

View File

@ -1,8 +1,10 @@
from base64 import urlsafe_b64decode
from django.contrib.auth.decorators import login_required
from django.db.models import Count
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.shortcuts import get_object_or_404, render
from shiftregister.fallback.models import TeamMember
# Create your views here.

View File

@ -1,6 +1,7 @@
from .models import Feedback
from django.forms import ModelForm
from django import forms
from django.forms import ModelForm
from .models import Feedback
class FeedbackForm(ModelForm):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.0.4 on 2023-05-25 14:16
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,8 +1,9 @@
# Generated by Django 4.0.4 on 2023-05-27 17:48
from django.db import migrations, models
import secrets
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [

View File

@ -1,5 +1,7 @@
from django.db import models
from secrets import token_urlsafe
from django.db import models
from shiftregister.app.models import Helper

View File

@ -1,6 +1,7 @@
from django.dispatch import receiver
from django.shortcuts import reverse
from dynamic_preferences.registries import global_preferences_registry
from shiftregister.core.signals import populate_nav
global_preferences = global_preferences_registry.manager()

View File

@ -1,9 +1,11 @@
from django.shortcuts import render, get_object_or_404
from .forms import FeedbackForm
from .models import Feedback, ShareToken
from shiftregister.app.models import LoginToken
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404, render
from shiftregister.app.models import LoginToken
from .forms import FeedbackForm
from .models import Feedback, ShareToken
def feedback(request, token):

View File

@ -1,4 +1,5 @@
from django.contrib import admin
from .models import Calendar

View File

@ -1,9 +1,11 @@
from datetime import timezone
import requests
from django.conf import settings
from django.db import transaction
from icalendar import Calendar
from .models import Event, Room, Shift
import requests
def import_calendar(calendar):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.0.4 on 2022-04-27 14:11
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,4 +1,5 @@
from django.db import models
from shiftregister.app.models import *

View File

@ -1,4 +1,5 @@
from celery import shared_task
from .importer import import_calendar
from .models import Calendar

View File

@ -1,7 +1,8 @@
from django.db import models
from django.db.models import Count, Case, F, When, Sum
from django.db.models import Case, Count, F, Sum, When
from django.http import HttpResponse
from shiftregister.app.models import Helper, Room, Shift, ShiftRegistration, Message
from shiftregister.app.models import Helper, Message, Room, Shift, ShiftRegistration
from shiftregister.fallback.models import FallbackAssignment
from shiftregister.importer.models import Event
@ -159,9 +160,11 @@ def metrics(request):
),
(
"worked_seconds_total",
(
worked_seconds_total.total_seconds()
if worked_seconds_total
else 0.0,
else 0.0
),
),
(
"worked_shifts_total",

View File

@ -1,7 +1,9 @@
from django.contrib import admin
from .models import Page
from pathlib import Path
from django.contrib import admin
from .models import Page
def reimport(modeladmin, request, queryset):
for page in queryset:
@ -13,6 +15,21 @@ def reimport(modeladmin, request, queryset):
@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
fields = ("url", "content", "title", "visible", "kind")
list_display = ("url", "title", "visible", "kind")
fields = (
"url",
"content",
"title",
"visible",
"kind",
"show_in_footer_nav",
"show_in_main_nav",
)
list_display = (
"url",
"title",
"visible",
"kind",
"show_in_footer_nav",
"show_in_main_nav",
)
actions = (reimport,)

View File

@ -1,7 +1,9 @@
from django.apps import AppConfig
from pathlib import Path
class PagesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "shiftregister.pages"
def ready(self):
from . import signals

View File

@ -1,7 +1,9 @@
from pathlib import Path
from bs4 import BeautifulSoup
from django.core.management.base import BaseCommand, CommandError
from ...models import Page
from pathlib import Path
class Command(BaseCommand):

View File

@ -0,0 +1,23 @@
# Generated by Django 5.0.4 on 2024-05-03 21:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pages", "0002_page_kind_alter_page_title"),
]
operations = [
migrations.AddField(
model_name="page",
name="show_in_footer_nav",
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name="page",
name="show_in_main_nav",
field=models.BooleanField(default=False),
),
]

View File

@ -16,6 +16,8 @@ class Page(models.Model):
visible = models.BooleanField(default=True)
title = models.CharField(blank=True, max_length=200)
kind = models.CharField(choices=KIND_CHOICES, default=REGULAR, max_length=8)
show_in_footer_nav = models.BooleanField(default=True)
show_in_main_nav = models.BooleanField(default=False)
def __str__(self):
return (

View File

@ -0,0 +1,30 @@
from django.dispatch import receiver
from django.urls import reverse
from shiftregister.core.signals import populate_footer_nav, populate_nav
from .models import Page
@receiver(populate_footer_nav, dispatch_uid="populate_pages_footer_nav")
def populate_pages_footer_nav(sender, **kwargs):
return [
{
"link": reverse("pages:view", args=(page.url,)),
"text": page.title or page.url,
}
for page in Page.objects.filter(visible=True)
if page.visible and page.show_in_footer_nav
]
@receiver(populate_nav, dispatch_uid="populate_pages_nav")
def populate_pages_nav(sender, **kwargs):
return [
{
"link": reverse("pages:view", args=(page.url,)),
"text": page.title or page.url,
}
for page in Page.objects.filter(visible=True)
if page.visible and page.show_in_main_nav
]

View File

@ -1,8 +1,9 @@
from django.views.generic import DetailView
from django.shortcuts import redirect
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.http import HttpResponseNotFound
from django.shortcuts import redirect
from django.views.generic import DetailView
from .models import Page
# Create your views here.

View File

@ -10,12 +10,13 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
from pathlib import Path
from os import getenv
from pathlib import Path
import sentry_sdk
from django.contrib.messages import constants as messages
from sentry_sdk.integrations.celery import CeleryIntegration
from sentry_sdk.integrations.django import DjangoIntegration
from django.contrib.messages import constants as messages
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -39,15 +40,15 @@ ALLOWED_HOSTS = list(filter(lambda s: s != "", getenv("ALLOWED_HOSTS", "").split
INSTALLED_APPS = [
"dynamic_preferences",
"shiftregister.app.apps.AppConfig",
"shiftregister.core.apps.CoreConfig",
"shiftregister.fallback.apps.FallbackConfig",
"shiftregister.importer.apps.ImporterConfig",
"shiftregister.metrics.apps.MetricsConfig",
"shiftregister.pages.apps.PagesConfig",
"shiftregister.signage.apps.SignageConfig",
"shiftregister.team.apps.TeamConfig",
"shiftregister.feedback.apps.FeedbackConfig",
"shiftregister.app",
"shiftregister.core",
"shiftregister.fallback",
"shiftregister.importer",
"shiftregister.metrics",
"shiftregister.pages",
"shiftregister.signage",
"shiftregister.team",
"shiftregister.feedback",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
@ -85,6 +86,7 @@ TEMPLATES = [
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"shiftregister.app.context_processors.proc",
"shiftregister.core.context_processors.footer_nav",
"shiftregister.core.context_processors.nav",
],
},
@ -146,10 +148,6 @@ USE_TZ = True
STATIC_ROOT = "/opt/shiftregister/static"
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR / "assets",
]
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
@ -178,7 +176,7 @@ CELERY_BEAT_SCHEDULE = {
},
"deactivate-fallbacks-every-300-seconds": {
"task": "shiftregister.fallback.tasks.deactivate_fallbacks",
"schedule": float(getenv("FALLBACK_DEACTIVAtE_INTERVAL", 300.0)), # seconds
"schedule": float(getenv("FALLBACK_DEACTIVATE_INTERVAL", 300.0)), # seconds
},
}

View File

@ -1,7 +1,7 @@
# Generated by Django 4.0.4 on 2022-05-18 13:10
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,2 +1,3 @@
from django.db import models
from shiftregister.app.models import *

View File

@ -1,8 +1,10 @@
from datetime import timedelta
from django.db import models
from django.db.models import Case, Count, F, ExpressionWrapper, Q, Sum, When
from django.db.models import Case, Count, ExpressionWrapper, F, Q, Sum, When
from django.shortcuts import render
from django.utils import timezone
from .models import Helper, Shift, ShiftRegistration

View File

@ -1,4 +1,5 @@
from django.contrib import admin
from .models import IncomingMessage, RoomViewToken
# Register your models here.

View File

@ -1,5 +1,6 @@
from django import forms
from .models import ShiftRegistration, Helper
from .models import Helper, ShiftRegistration
# placeholder form for simple submit button use cases so we get csrf protection

View File

@ -1,7 +1,7 @@
# Generated by Django 4.0.4 on 2023-05-06 23:27
from django.db import migrations, models
import phonenumber_field.modelfields
from django.db import migrations, models
class Migration(migrations.Migration):

View File

@ -1,7 +1,8 @@
# Generated by Django 4.0.4 on 2023-05-18 15:15
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
import shiftregister.team.models

View File

@ -1,7 +1,9 @@
import secrets
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField
from shiftregister.app.models import *
import secrets
# Create your models here.

View File

@ -1,7 +1,9 @@
from django.dispatch import receiver
from django.shortcuts import reverse
from dynamic_preferences.registries import global_preferences_registry
from shiftregister.core.signals import populate_nav
from .models import IncomingMessage
global_preferences = global_preferences_registry.manager()

View File

@ -1,8 +1,10 @@
import sentry_sdk
from celery import shared_task
from django.conf import settings
from shiftregister.app.sipgate.history import list_incoming_sms
from .models import IncomingMessage
import sentry_sdk
@shared_task

View File

@ -1,6 +1,7 @@
from django import template
from re import ASCII, sub
from django import template
register = template.Library()

View File

@ -1,26 +1,27 @@
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from django.db.models.fields import DateTimeField
from django.db.models import F, Count, Q, ExpressionWrapper, Case, When
from .models import (
ShiftRegistration,
Room,
Shift,
Helper,
Message,
IncomingMessage,
RoomViewToken,
)
from django.views.generic import DetailView, ListView
from django.views.generic.edit import FormMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.db import models, transaction
from django.core.paginator import Paginator
from .forms import BulkMessage, HelperShift, HelperMessage
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db import models, transaction
from django.db.models import Case, Count, ExpressionWrapper, F, Q, When
from django.db.models.fields import DateTimeField
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views.generic import DetailView, ListView
from django.views.generic.edit import FormMixin
from .forms import BulkMessage, HelperMessage, HelperShift
from .models import (
Helper,
IncomingMessage,
Message,
Room,
RoomViewToken,
Shift,
ShiftRegistration,
)
# Create your views here.

View File

@ -13,6 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path