Commit 32ccfe49 authored by Felix Schlösser / TinTin's avatar Felix Schlösser / TinTin
Browse files

moved from abstract meeting to model inheritance

parent aecbcd9c
Pipeline #164925 failed with stages
in 15 seconds
from django.db import models
# Managers
class DecisionManager(models.Manager):
def create_decision(self, agenda_item, has_passed, note=None):
if has_passed:
voting_result = AbstractDecision.VOTING_RESULT_CHOICES["ACCEPTED"]
voting_result = Decision.VOTING_RESULT_CHOICES["ACCEPTED"]
else:
voting_result = AbstractDecision.VOTING_RESULT_CHOICES["REJECTED"]
voting_result = Decision.VOTING_RESULT_CHOICES["REJECTED"]
decision = self.create(
agenda_item=agenda_item, voting_result=voting_result, note=note
......
......@@ -13,7 +13,7 @@ from datetime import datetime, timedelta
from .managers import DecisionManager
# Create your models here.
class AbstractDecision(TimeStampedModel):
class Decision(TimeStampedModel):
id = HashidAutoField(primary_key=True)
VOTING_RESULT_CHOICES = [
("ACCEPTED", _("angenommen")),
......@@ -21,6 +21,10 @@ class AbstractDecision(TimeStampedModel):
("REJECTED", _("abgelehnt")),
]
agenda_item = models.OneToOneField(
"meetings.AgendaItem", on_delete=models.PROTECT
)
_voting_result = StatusField(
choices_name="VOTING_RESULT_CHOICES", verbose_name=_("Anstimmungsergebnis")
)
......@@ -30,12 +34,6 @@ class AbstractDecision(TimeStampedModel):
objects = DecisionManager()
class Meta:
verbose_name = _("Beschluss")
verbose_name_plural = _("Beschlüsse")
ordering = ["created"]
abstract = True
def __str__(self):
return f"{ self.motion.title }, {self.voting_result}"
......@@ -90,29 +88,21 @@ class AbstractDecision(TimeStampedModel):
super().save(*args, **kwargs)
class Decision(AbstractDecision):
agenda_item = models.OneToOneField("meetings.AgendaItem", on_delete=models.PROTECT)
class Meta(AbstractDecision.Meta):
class Meta:
ordering = ["created"]
verbose_name = _("AStA-Beschluss")
verbose_name_plural = _("AStA-Beschlüsse")
class BoardDecision(AbstractDecision):
agenda_item = models.OneToOneField(
"meetings.BoardMeetingAgendaItem", on_delete=models.PROTECT
)
class Meta(AbstractDecision.Meta):
class BoardDecision(Decision):
class Meta(Decision.Meta):
verbose_name = _("Vorstandsbeschluss")
verbose_name_plural = _("Vorstandsbeschlüsse")
class ExtendedBoardDecision(AbstractDecision):
agenda_item = models.OneToOneField(
"meetings.ExtendedBoardMeetingAgendaItem", on_delete=models.PROTECT
)
class ExtendedBoardDecision(Decision):
class Meta(BoardDecision.Meta):
verbose_name = _("erweiterter Vorstandsbeschluss")
verbose_name_plural = _("erweiterter Vorstandsbeschlüsse")
......@@ -21,14 +21,6 @@ class ExtendedBoardMeetingAdmin(admin.ModelAdmin):
class AgendaItemAdmin(admin.ModelAdmin):
list_display = ('__str__', 'meeting')
@admin.register(BoardMeetingAgendaItem)
class BoardMeetingAgendaItemAdmin(admin.ModelAdmin):
list_display = ('__str__', 'meeting')
@admin.register(ExtendedBoardMeetingAgendaItem)
class ExtendedBoardMeetingAgendaItemAdmin(admin.ModelAdmin):
list_display = ('__str__', 'meeting')
@admin.register(LegislativePeriod)
class LegislativePeriodAdmin(admin.ModelAdmin):
......
......@@ -302,7 +302,7 @@ class LegislativePeriod(models.Model):
return None
class AbstractMeeting(TimeStampedModel):
class Meeting(TimeStampedModel):
head = models.ForeignKey(
"users.User",
on_delete=models.CASCADE,
......@@ -311,13 +311,14 @@ class AbstractMeeting(TimeStampedModel):
verbose_name="Sitzungsleitung",
)
date = models.DateField(
default=get_next_meeting_date,
verbose_name=_("Datum"),
default=get_current_date,
validators=[
present_or_future_date,
],
)
time = models.TimeField(default=get_current_time_plus_2_minutes, verbose_name=_("Uhrzeit"))
time = models.TimeField(default=time(hour=14, minute=15), verbose_name=_("Uhrzeit"))
first_invitation_send_datetime = models.DateTimeField(
null=True,
unique=True,
......@@ -349,12 +350,6 @@ class AbstractMeeting(TimeStampedModel):
default=Format.get_favorite,
)
class Meta:
verbose_name = _("Sitzung")
verbose_name_plural = _("Sitzungen")
ordering = ["date", "time"]
abstract = True
def clean(self):
if self.date < self.legislative_period.begin:
raise ValidationError(
......@@ -457,21 +452,11 @@ class AbstractMeeting(TimeStampedModel):
pass
# sendmail()
class Meeting(AbstractMeeting):
date = models.DateField(
default=get_next_meeting_date,
verbose_name=_("Datum"),
validators=[
present_or_future_date,
],
)
time = models.TimeField(default=time(hour=14, minute=15), verbose_name=_("Uhrzeit"))
class Meta(AbstractMeeting.Meta):
verbose_name = _("AStA-Sitzung")
verbose_name_plural = _("AStA-Sitzungen")
class Meta:
verbose_name = _("Sitzung")
verbose_name_plural = _("Sitzungen")
ordering = ["date", "time"]
constraints = [
models.CheckConstraint(
name="first_meeting_invitation_before_last_end",
......@@ -511,8 +496,9 @@ class Meeting(AbstractMeeting):
class BoardMeeting(AbstractMeeting):
class Meta(AbstractMeeting.Meta):
class BoardMeeting(Meeting):
class Meta(Meeting.Meta):
verbose_name = _("Vorstandssitzung")
verbose_name_plural = _("Vorstandssitzungen")
constraints = [
......@@ -548,10 +534,12 @@ class BoardMeeting(AbstractMeeting):
next_item = BoardMeetingAgendaItem.objects.create_agenda_item(self, has_passed, note)
return next_item
class ExtendedBoardMeeting(AbstractMeeting):
class ExtendedBoardMeeting(Meeting):
pass
class Meta(AbstractMeeting.Meta):
class Meta(Meeting.Meta):
verbose_name = _("erweiterte Vorstandssitzung")
verbose_name_plural = _("erweiterte Vorstandssitzungen")
constraints = [
......@@ -590,7 +578,7 @@ class ExtendedBoardMeeting(AbstractMeeting):
class AbstractAgendaItem(TimeStampedModel):
class AgendaItem(TimeStampedModel):
sort_order = models.IntegerField(
editable=False,
default = 1,
......@@ -605,13 +593,16 @@ class AbstractAgendaItem(TimeStampedModel):
verbose_name=_("Antrag"),
)
meeting = models.ForeignKey(
Meeting,
default=Meeting.get_next,
related_name="_%(class)ss",
on_delete=models.CASCADE,
verbose_name=_("Sitzung"),
)
objects = AgendaItemManager()
class Meta:
ordering = ["meeting", "sort_order"]
unique_together = [['meeting', 'motion'],
['meeting', 'sort_order']]
abstract = True
def __str__(self):
return f"TOP { self.sort_order }: { self.motion.title }"
......@@ -675,7 +666,7 @@ class AbstractAgendaItem(TimeStampedModel):
)
def save(self, *args, **kwargs):
# Only one can be the favorite
# Increment sort order
with transaction.atomic():
agenda_items = self.__class__.objects.filter(meeting=self.meeting)
if agenda_items.exists():
......@@ -686,55 +677,15 @@ class AbstractAgendaItem(TimeStampedModel):
super().save(*args, **kwargs)
class AgendaItem(AbstractAgendaItem):
meeting = models.ForeignKey(
Meeting,
default=Meeting.get_next,
related_name="_%(class)ss",
on_delete=models.CASCADE,
verbose_name=_("Sitzung"),
)
class Meta(AbstractAgendaItem.Meta):
class Meta:
ordering = ["meeting", "sort_order"]
unique_together = [['meeting', 'motion'],
['meeting', 'sort_order']]
verbose_name = _("Tagesordnungspunkt")
verbose_name_plural = _("Tagesordnungspunkte")
def vote(self, has_passed, note=None):
decision = Decision.objects.create_decision(self, has_passed, note)
decision = self.__class__.objects.create_decision(self, has_passed, note)
return decision
class BoardMeetingAgendaItem(AbstractAgendaItem):
meeting = models.ForeignKey(
BoardMeeting,
default=BoardMeeting.get_next,
related_name="_%(class)ss",
on_delete=models.CASCADE,
verbose_name=_("Sitzung"),
)
class Meta(AbstractAgendaItem.Meta):
verbose_name = _("Tagesordnungspunkt (Vorstandssitzung)")
verbose_name_plural = _("Tagesordnungspunkte (Vorstandssitzung)")
def vote(self, has_passed, note=None):
decision = BoardDecision.objects.create_decision(self, has_passed, note)
return decision
class ExtendedBoardMeetingAgendaItem(AbstractAgendaItem):
meeting = models.ForeignKey(
ExtendedBoardMeeting,
default=BoardMeeting.get_next,
related_name="_%(class)ss",
on_delete=models.CASCADE,
verbose_name=_("Sitzung"),
)
class Meta(AbstractAgendaItem.Meta):
verbose_name = _("Tagesordnungspunkt (erweiterte Vorstandssitzung)")
verbose_name_plural = _("Tagesordnungspunkte (erweiterte Vorstandssitzung)")
def vote(self, has_passed, note=None):
decision = ExtendedBoardDecision.objects.create_decision(self, has_passed, note)
return decision
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
from django.urls import include, path
from django.views.generic import TemplateView
from django.views import defaults as default_views
from django.utils.translation import gettext_lazy as _
from .views import *
app_name = "motion"
urlpatterns = [] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += i18n_patterns(
path(settings.ADMIN_URL, admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
path("", DashboardView.as_view(), name="dashboard"),
path(_("entwürfe"), PersonalDraftListView.as_view(), name="my-drafts"),
path(_("entwurf/<str:pk>"), DraftDetailView.as_view(), name="draft-detail"),
path(_("entwurf/<str:pk>/löschen"), DraftDeleteView.as_view(), name="draft-delete"),
path(_("meine-anträge"), PersonalMotionListView.as_view(), name="my-motions"),
path(_("antrag/neu"), MotionCreateView.as_view(), name="submit-motion"),
path(_("antrag/<str:pk>"), MotionDetailView.as_view(), name="motion-detail"),
path(
_("antrag/<str:pk>/ändern"),
MotionUpdateView.as_view(),
name="motion-change",
),
path(
_("antrag/<str:pk>/zurückziehen"),
MotionUpdateStatusView.as_view(),
name="motion-withdraw",
),
path(
_("finanzantrag/anschaffung/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request-purchase",
),
path(
_("finanzantrag/veranstaltung/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request-event",
),
path(
_("finanzantrag/aktivität/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request-activity",
),
path(
_("finanzantrag/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request",
),
path(
_("finanzantrag/<str:pk>"),
FinancialRequestDetailView.as_view(),
name="financial-request-detail",
),
path(
_("finanzantrag/<str:pk>/ändern"),
FinancialRequestUpdateView.as_view(),
name="financial-request-change",
),
path(_("archiv/"), MotionArchiveIndexView.as_view(), name="archive"),
path(_("archiv/suche"), MotionSeachView.as_view(), name="archive-search"),
path(_("archiv/int:year>/"), MotionYearArchiveView.as_view(), name="archive-year"),
path(
_("archiv/<int:year>/<str:month>/"),
MotionMonthArchiveView.as_view(),
name="archive-month",
),
path(
_("archiv/<int:year>/woche/<int:week>/"),
MotionWeekArchiveView.as_view(),
name="archive-week",
),
path(_("benutzer/"), include("django.contrib.auth.urls"), name="account"),
prefix_default_language=False,
)
if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like.
urlpatterns += [
path(
"400/",
default_views.bad_request,
kwargs={"exception": Exception("Bad Request!")},
),
path(
"403/",
default_views.permission_denied,
kwargs={"exception": Exception("Permission Denied")},
),
path(
"404/",
default_views.page_not_found,
kwargs={"exception": Exception("Page not Found")},
),
path("500/", default_views.server_error),
]
if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns
......@@ -14,7 +14,6 @@ class TranslatedMotionAdmin(TranslationAdmin):
def get_queryset(self, request):
qs = Motion.drafts.all() | Motion.objects.all()
print(Motion.drafts.all())
return qs
......
......@@ -257,9 +257,9 @@ class CostItemForm(forms.ModelForm):
class RequiredFormSet(BaseInlineFormSet):
queryset = FinancialRequest.objects.including_drafts().all()
queryset = FinancialRequest.objects.include_drafts().all()
CostItemFormset = inlineformset_factory(
FinancialRequest, CostItem, form=CostItemForm, extra=2, min_num=1, formset=RequiredFormSet
FinancialRequest, CostItem, form=CostItemForm, extra=1, min_num=1, formset=RequiredFormSet
)
......@@ -6,10 +6,16 @@ class DraftMotionManager(InheritanceManager):
def get_queryset(self):
return super().get_queryset().filter(_status='DRAFT')
def include_withdranws(self):
return super().get_queryset().filter(models.Q(_status='DRAFT') |
models.Q(_status='WITHDRAWN'))
class NoDraftsMotionManager(InheritanceManager):
def get_queryset(self):
return super().get_queryset().exclude(_status='DRAFT').exclude(_status='WITHDRAWN')
def include_withdranws(self):
return super().get_queryset().exclude(_status='DRAFT')
def including_drafts(self):
def include_drafts(self):
return super().get_queryset()
......@@ -7,7 +7,6 @@ import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import djmoney.models.fields
import hashid_field.field
import model_utils.fields
import simple_history.models
......@@ -27,8 +26,6 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, verbose_name='Name')),
('note', models.CharField(max_length=256, verbose_name='Erläuternde Bemerkung')),
('price_currency', djmoney.models.fields.CurrencyField(choices=[('EUR', 'Euro')], default='EUR', editable=False, max_length=3)),
('price', djmoney.models.fields.MoneyField(decimal_places=2, default_currency='EUR', max_digits=8, verbose_name='Preis')),
('links', django.contrib.postgres.fields.ArrayField(base_field=models.URLField(), blank=True, help_text='Mehrere URLs durch Komma trennen.', null=True, size=3, verbose_name='Links')),
],
options={
......@@ -117,8 +114,6 @@ class Migration(migrations.Migration):
name='FinancialRequest',
fields=[
('motion_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='motions.motion')),
('amount_currency', djmoney.models.fields.CurrencyField(choices=[('EUR', 'Euro')], default='EUR', editable=False, max_length=3)),
('amount', djmoney.models.fields.MoneyField(decimal_places=2, default_currency='EUR', max_digits=7, verbose_name='Höhe')),
],
options={
'verbose_name': 'Finanzantrag',
......
......@@ -14,22 +14,6 @@ class Migration(migrations.Migration):
name='costitem',
options={'ordering': ['price_euro'], 'verbose_name': 'Kostenpunkt', 'verbose_name_plural': 'Kostenpunkte'},
),
migrations.RemoveField(
model_name='costitem',
name='price',
),
migrations.RemoveField(
model_name='costitem',
name='price_currency',
),
migrations.RemoveField(
model_name='financialrequest',
name='amount',
),
migrations.RemoveField(
model_name='financialrequest',
name='amount_currency',
),
migrations.AddField(
model_name='costitem',
name='price_euro',
......
......@@ -350,11 +350,23 @@ class Motion(MotionStatusMixin, TimeStampedModel):
def status(self):
latest_decision = self.get_latest_decision()
if latest_decision:
if latest_decision and self.is_decided:
return latest_decision.voting_result
elif self.is_decided:
logging.error("Decided status without prior decisions .")
logging.error(f"Status: {self._status}")
logging.error(f"Decision: {self.get_latest_decision()}")
return _("INVALID")
elif latest_decision:
logging.error("Prior decisions without decided status.")
logging.error(f"Status: {self._status}")
logging.error(f"Decision: {self.get_latest_decision()}")
return _("INVALID")
elif not latest_decision and self.is_reopened:
logging.error("Reopened motion without prior decisions.")
return _("Ohne Beschluss")
logging.error(f"Status: {self._status}")
logging.error(f"Decision: {self.get_latest_decision()}")
return _("INVALID")
else:
return self.get__status_display()
......@@ -440,6 +452,12 @@ class Motion(MotionStatusMixin, TimeStampedModel):
meetings.sort(key=lambda x: x.date)
return meetings
def get_future_agenda_items(self):
agenda_items = self._get_agenda_items()
now = datetime.now()
future_agenda_items = [item for item in agenda_items if item.meeting.datetime < now]
return future_agenda_items
def get_future_meetings(self):
meetings = self.get_meetings()
now = datetime.now()
......
......@@ -18,10 +18,10 @@ urlpatterns += i18n_patterns(
path(settings.ADMIN_URL, admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
path("", DashboardView.as_view(), name="dashboard"),
path(_("entwürfe"), DraftListView.as_view(), name="my-drafts"),
path(_("entwürfe"), PersonalDraftListView.as_view(), name="my-drafts"),
path(_("entwurf/<str:pk>"), DraftDetailView.as_view(), name="draft-detail"),
path(_("entwurf/<str:pk>/löschen"), DraftDeleteView.as_view(), name="draft-delete"),
path(_("meine-anträge"), MotionListView.as_view(), name="my-motions"),
path(_("meine-anträge"), PersonalMotionListView.as_view(), name="my-motions"),
path(_("antrag/neu"), MotionCreateView.as_view(), name="submit-motion"),
path(_("antrag/<str:pk>"), MotionDetailView.as_view(), name="motion-detail"),
path(
......@@ -35,6 +35,21 @@ urlpatterns += i18n_patterns(
name="motion-withdraw",
),
path(
_("finanzantrag/anschaffung/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request-purchase",
),
path(
_("finanzantrag/veranstaltung/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request-event",
),
path(
_("finanzantrag/aktivität/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request-activity",
),
path(
_("finanzantrag/neu"),
FinancialRequestCreateView.as_view(),
name="submit-financial-request",
......@@ -50,6 +65,7 @@ urlpatterns += i18n_patterns(
name="financial-request-change",
),
path(_("archiv/"), MotionArchiveIndexView.as_view(), name="archive"),
path(_("archiv/suche"), MotionSeachView.as_view(), name="archive-search"),
path(_("archiv/int:year>/"), MotionYearArchiveView.as_view(), name="archive-year"),
path(
_("archiv/<int:year>/<str:month>/"),
......
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.views import generic
from django.views.generic.dates import (
ArchiveIndexView,
......@@ -5,6 +8,7 @@ from django.views.generic.dates import (
MonthArchiveView,
WeekArchiveView,
)
from django.views.defaults import bad_request
from django.utils.translation import get_language, gettext_lazy as _
from django.contrib.messages.views import SuccessMessageMixin
......@@ -29,19 +33,19 @@ class DashboardView(generic.TemplateView):
return context
class MotionListView(LoginRequiredMixin, generic.ListView):
class PersonalMotionListView(LoginRequiredMixin, generic.ListView):