Convert from MariaDB to PostgreSQL
MariaDB has become far too annoying/buggy, and there are some neat features only available in PostgreSQL
This commit is contained in:
parent
97b746ba3a
commit
ee61451759
@ -9,21 +9,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: catthehacker/ubuntu:act-latest
|
||||
services:
|
||||
mariadb:
|
||||
# TODO: this is pinned to avoid what apears to be a bug with
|
||||
# MariaDB >= 10.11.9, and collation issues with 11.x.x
|
||||
image: mariadb:10.11.8
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: whatever
|
||||
POSTGRES_PASSWORD: whatever
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"healthcheck.sh",
|
||||
"--su-mysql",
|
||||
"--connect",
|
||||
"--innodb_initialized",
|
||||
]
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup PDM
|
||||
@ -35,7 +26,7 @@ jobs:
|
||||
- name: Install apt dependencies
|
||||
run: >-
|
||||
sudo apt-get update &&
|
||||
sudo apt-get -y install build-essential python3-dev libldap2-dev libsasl2-dev mariadb-client
|
||||
sudo apt-get -y install build-essential python3-dev libldap2-dev libsasl2-dev
|
||||
- name: Install python dependencies
|
||||
run: pdm sync -d -G dev
|
||||
|
||||
|
@ -28,7 +28,7 @@ class Base(Configuration):
|
||||
@classmethod
|
||||
def setup(cls):
|
||||
super().setup()
|
||||
cls.DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"}
|
||||
cls.DATABASES["default"]["OPTIONS"] = {"pool": True}
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"dal",
|
||||
@ -52,7 +52,6 @@ class Base(Configuration):
|
||||
"django_tables2",
|
||||
"django_filters",
|
||||
"django_db_views",
|
||||
"django_mysql",
|
||||
"django_sendfile",
|
||||
"django_bootstrap5",
|
||||
# "tasks.apps.TasksConfig",
|
||||
@ -106,9 +105,6 @@ class Base(Configuration):
|
||||
|
||||
WSGI_APPLICATION = "cmsmanage.wsgi.application"
|
||||
|
||||
# mysql.W003 (unique CharField length) is irrelevant on MariaDB >= 10.4.3
|
||||
SILENCED_SYSTEM_CHECKS = ["mysql.W003"]
|
||||
|
||||
# Default URL to redirect to after authentication
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
LOGIN_URL = "/auth/login/"
|
||||
@ -213,6 +209,9 @@ class Base(Configuration):
|
||||
# CMSManage specific stuff
|
||||
WIKI_URL = values.URLValue("https://wiki.claremontmakerspace.org")
|
||||
|
||||
# ID of flag for Members folder in MembershipWorks
|
||||
MW_MEMBERS_FOLDER_ID = "5771675edcdf126302a2f6b9"
|
||||
|
||||
|
||||
class NonCIBase(Base):
|
||||
"""required for all but CI"""
|
||||
@ -367,13 +366,10 @@ class CI(Base):
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"HOST": "mariadb",
|
||||
"NAME": "CMS_Database",
|
||||
"USER": "root",
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"HOST": "postgres",
|
||||
"NAME": "cms",
|
||||
"USER": "postgres",
|
||||
"PASSWORD": "whatever",
|
||||
"OPTIONS": {
|
||||
"charset": "utf8mb4",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,40 @@
|
||||
# Generated by Django 4.1.3 on 2023-01-25 02:18
|
||||
# Generated by Django 5.1 on 2024-08-21 18:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
dependencies = [
|
||||
("membershipworks", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Door",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=64, unique=True)),
|
||||
("ip", models.GenericIPAddressField(protocol="IPv4")),
|
||||
(
|
||||
"access_field",
|
||||
models.TextField(
|
||||
help_text="Membershipworks field that grants members access to this door",
|
||||
max_length=128,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="HIDEvent",
|
||||
fields=[
|
||||
@ -21,7 +47,6 @@ class Migration(migrations.Migration):
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("door_name", models.CharField(db_column="doorName", max_length=64)),
|
||||
("timestamp", models.DateTimeField()),
|
||||
(
|
||||
"event_type",
|
||||
@ -88,16 +113,173 @@ class Migration(migrations.Migration):
|
||||
blank=True, db_column="rawCardNumber", max_length=8, null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"door",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.door",
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_red",
|
||||
models.GeneratedField(
|
||||
db_persist=True,
|
||||
expression=models.Q(
|
||||
(
|
||||
"event_type__in",
|
||||
[
|
||||
1022,
|
||||
1023,
|
||||
2024,
|
||||
2029,
|
||||
2036,
|
||||
2042,
|
||||
2043,
|
||||
2046,
|
||||
4041,
|
||||
4042,
|
||||
4043,
|
||||
4044,
|
||||
4045,
|
||||
],
|
||||
)
|
||||
),
|
||||
output_field=models.BooleanField(),
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"db_table": "hidevent",
|
||||
"ordering": ("-timestamp",),
|
||||
"constraints": [
|
||||
models.UniqueConstraint(
|
||||
fields=("door", "timestamp", "event_type"),
|
||||
name="unique_hidevent",
|
||||
)
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="hidevent",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("door_name", "timestamp", "event_type"), name="unique_hidevent"
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="DoorCardholderMember",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("cardholder_id", models.IntegerField()),
|
||||
(
|
||||
"door",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.door",
|
||||
),
|
||||
),
|
||||
(
|
||||
"member",
|
||||
models.ForeignKey(
|
||||
db_constraint=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="membershipworks.member",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"constraints": [
|
||||
models.UniqueConstraint(
|
||||
fields=("door", "cardholder_id"),
|
||||
name="unique_door_cardholder_id",
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=("door", "member"), name="unique_door_member"
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Schedule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FlagScheduleRule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("doors", models.ManyToManyField(to="doorcontrol.door")),
|
||||
(
|
||||
"flag",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.flag",
|
||||
),
|
||||
),
|
||||
(
|
||||
"schedule",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.schedule",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AttributeScheduleRule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"access_field",
|
||||
models.CharField(
|
||||
help_text="Membershipworks field that grants members access to this door using this schedule.",
|
||||
max_length=128,
|
||||
),
|
||||
),
|
||||
("doors", models.ManyToManyField(to="doorcontrol.door")),
|
||||
(
|
||||
"schedule",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.schedule",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -1,74 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-19 04:20
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def link_events_to_doors(apps, schema_editor):
|
||||
HIDEvent = apps.get_model("doorcontrol", "HIDEvent")
|
||||
Door = apps.get_model("doorcontrol", "Door")
|
||||
for event in HIDEvent.objects.all():
|
||||
door, created = Door.objects.get_or_create(name=event.door_name)
|
||||
event.door = door
|
||||
event.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="hidevent",
|
||||
options={"ordering": ("-timestamp",)},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Door",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=64, unique=True)),
|
||||
],
|
||||
),
|
||||
# create nullable foreign key to door
|
||||
migrations.AddField(
|
||||
model_name="hidevent",
|
||||
name="door",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.door",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
# create new Doors and link them to HID Events
|
||||
migrations.RunPython(link_events_to_doors, atomic=True),
|
||||
# make door foreign key not nullable
|
||||
migrations.AlterField(
|
||||
model_name="hidevent",
|
||||
name="door",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE, to="doorcontrol.door"
|
||||
),
|
||||
),
|
||||
# remove old constaint
|
||||
migrations.RemoveConstraint(model_name="hidevent", name="unique_hidevent"),
|
||||
# remove old name field
|
||||
migrations.RemoveField(
|
||||
model_name="hidevent",
|
||||
name="door_name",
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="hidevent",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("door", "timestamp", "event_type"), name="unique_hidevent"
|
||||
),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-09-19 15:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0002_door_remove_hidevent_door_name_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="door",
|
||||
name="ip",
|
||||
field=models.GenericIPAddressField(default="", protocol="IPv4"),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -1,40 +0,0 @@
|
||||
# Generated by Django 5.0 on 2023-12-04 16:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0003_door_ip"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="hidevent",
|
||||
name="is_red",
|
||||
field=models.GeneratedField(
|
||||
db_persist=False,
|
||||
expression=models.Q(
|
||||
(
|
||||
"event_type__in",
|
||||
[
|
||||
1022,
|
||||
1023,
|
||||
2024,
|
||||
2029,
|
||||
2036,
|
||||
2042,
|
||||
2043,
|
||||
2046,
|
||||
4041,
|
||||
4042,
|
||||
4043,
|
||||
4044,
|
||||
4045,
|
||||
],
|
||||
)
|
||||
),
|
||||
output_field=models.BooleanField(),
|
||||
),
|
||||
),
|
||||
]
|
@ -1,56 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-09 16:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0004_hidevent_is_red"),
|
||||
("membershipworks", "0014_remove_eventext_details_timestamp"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DoorCardholderMember",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("cardholder_id", models.IntegerField()),
|
||||
(
|
||||
"door",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.door",
|
||||
),
|
||||
),
|
||||
(
|
||||
"member",
|
||||
models.ForeignKey(
|
||||
db_constraint=False,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="membershipworks.member",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="doorcardholdermember",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("door", "cardholder_id"), name="unique_door_cardholder_id"
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="doorcardholdermember",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("door", "member"), name="unique_door_member"
|
||||
),
|
||||
),
|
||||
]
|
@ -1,106 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-02-23 18:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("doorcontrol", "0005_doorcardholdermember_and_more"),
|
||||
("membershipworks", "0015_eventmeetingtime_end_after_start"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Schedule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=255, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="door",
|
||||
name="access_field",
|
||||
field=models.TextField(
|
||||
default="CHANGE ME",
|
||||
help_text="Membershipworks field that grants members access to this door",
|
||||
max_length=128,
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="FlagScheduleRule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("doors", models.ManyToManyField(to="doorcontrol.door")),
|
||||
(
|
||||
"flag",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.flag",
|
||||
),
|
||||
),
|
||||
(
|
||||
"schedule",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.schedule",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="AttributeScheduleRule",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"access_field",
|
||||
models.CharField(
|
||||
help_text="Membershipworks field that grants members access to this door using this schedule.",
|
||||
max_length=128,
|
||||
),
|
||||
),
|
||||
("doors", models.ManyToManyField(to="doorcontrol.door")),
|
||||
(
|
||||
"schedule",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="doorcontrol.schedule",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
]
|
@ -183,7 +183,7 @@ class HIDEvent(models.Model):
|
||||
]
|
||||
),
|
||||
output_field=models.BooleanField(),
|
||||
db_persist=False,
|
||||
db_persist=True,
|
||||
)
|
||||
|
||||
objects = HIDEventQuerySet.as_manager()
|
||||
|
@ -17,13 +17,15 @@ def get_cardholders(door: Door):
|
||||
member_id=cardholder.attrib.get("custom2"),
|
||||
)
|
||||
|
||||
cardholders = door.controller.get_cardholders()
|
||||
DoorCardholderMember.objects.bulk_create(
|
||||
(
|
||||
make_ch_member(cardholder)
|
||||
for cardholder in door.controller.get_cardholders()
|
||||
for cardholder in cardholders
|
||||
if "custom2" in cardholder.attrib
|
||||
),
|
||||
update_conflicts=True,
|
||||
unique_fields=("door", "cardholder_id"),
|
||||
update_fields=("member",),
|
||||
)
|
||||
|
||||
|
@ -2,8 +2,9 @@ import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.contrib.postgres.aggregates import StringAgg
|
||||
from django.core.exceptions import BadRequest
|
||||
from django.db.models import Count, F, FloatField, Q, Window
|
||||
from django.db.models import Count, F, FloatField, Func, Q, Value, Window
|
||||
from django.db.models.functions import Lead, Trunc
|
||||
from django.urls import path, reverse_lazy
|
||||
from django.utils.text import slugify
|
||||
@ -12,8 +13,6 @@ from django.views.generic.list import ListView
|
||||
import django_filters
|
||||
import django_tables2 as tables
|
||||
from django_filters.views import BaseFilterView
|
||||
from django_mysql.models.aggregates import GroupConcat
|
||||
from django_mysql.models.functions import ConcatWS
|
||||
from django_tables2 import SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
|
||||
@ -223,8 +222,10 @@ class MostActiveMembers(BaseAccessReport):
|
||||
.values("member_id")
|
||||
.annotate(
|
||||
access_count=Count("member_id"),
|
||||
name=GroupConcat(
|
||||
ConcatWS("forename", "surname", separator=" "), distinct=True
|
||||
name=StringAgg(
|
||||
Func(Value(" "), "forename", "surname", function="concat_ws"),
|
||||
", ",
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
.order_by("-access_count")
|
||||
@ -249,8 +250,10 @@ class DetailByDay(BaseAccessReport):
|
||||
"member_id",
|
||||
filter=Q(event_type__in=HIDEvent.EventType.any_granted_access()),
|
||||
),
|
||||
name=GroupConcat(
|
||||
ConcatWS("forename", "surname", separator=" "), distinct=True
|
||||
name=StringAgg(
|
||||
Func(Value(" "), "forename", "surname", function="concat_ws"),
|
||||
", ",
|
||||
distinct=True,
|
||||
),
|
||||
)
|
||||
.order_by("-timestamp__date")
|
||||
|
@ -1,13 +1,21 @@
|
||||
# Generated by Django 5.0 on 2023-12-20 05:40
|
||||
# Generated by Django 5.1 on 2024-08-21 18:17
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
from django.db.models.functions import Cast
|
||||
|
||||
import django_db_views.migration_functions
|
||||
import django_db_views.operations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
dependencies = [
|
||||
("reservations", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
@ -115,18 +123,6 @@ class Migration(migrations.Migration):
|
||||
blank=True, db_column="Parent Account ID", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"gift_membership_purchased_by",
|
||||
models.TextField(
|
||||
blank=True, db_column="Gift Membership purchased by", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"purchased_gift_membership_for",
|
||||
models.TextField(
|
||||
blank=True, db_column="Purchased Gift Membership for", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"closet_storage",
|
||||
models.TextField(
|
||||
@ -151,18 +147,6 @@ class Migration(migrations.Migration):
|
||||
db_column="Access Permitted Shops During Extended Hours?"
|
||||
),
|
||||
),
|
||||
(
|
||||
"normal_access_permitted_during_covid19_limited_operations",
|
||||
models.BooleanField(
|
||||
db_column="Normal Access Permitted During COVID-19 Limited Operations"
|
||||
),
|
||||
),
|
||||
(
|
||||
"access_permitted_during_covid19_staffed_period_only",
|
||||
models.BooleanField(
|
||||
db_column="Access Permitted During COVID-19 Staffed Period Only"
|
||||
),
|
||||
),
|
||||
(
|
||||
"access_front_door_and_studio_space_during_extended_hours",
|
||||
models.BooleanField(
|
||||
@ -349,18 +333,15 @@ class Migration(migrations.Migration):
|
||||
"liability_form_filled_out",
|
||||
models.BooleanField(db_column="Liability Form Filled Out"),
|
||||
),
|
||||
(
|
||||
"self_certify_essential_business",
|
||||
models.BooleanField(db_column="selfCertifyEssentialBusiness"),
|
||||
),
|
||||
(
|
||||
"accepted_covid19_policy",
|
||||
models.BooleanField(db_column="Accepted COVID-19 Policy"),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"db_table": "members",
|
||||
"ordering": ("first_name", "last_name"),
|
||||
"indexes": [
|
||||
models.Index(fields=["account_name"], name="account_name_idx"),
|
||||
models.Index(fields=["first_name"], name="first_name_idx"),
|
||||
models.Index(fields=["last_name"], name="last_name_idx"),
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@ -386,6 +367,7 @@ class Migration(migrations.Migration):
|
||||
"member",
|
||||
models.ForeignKey(
|
||||
db_column="uid",
|
||||
db_constraint=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.member",
|
||||
),
|
||||
@ -393,6 +375,11 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
options={
|
||||
"db_table": "memberflag",
|
||||
"constraints": [
|
||||
models.UniqueConstraint(
|
||||
fields=("member", "flag"), name="unique_member_flag"
|
||||
)
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
@ -416,7 +403,7 @@ class Migration(migrations.Migration):
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("sid", models.CharField(blank=True, max_length=27, null=True)),
|
||||
("sid", models.CharField(blank=True, max_length=256, null=True)),
|
||||
("timestamp", models.DateTimeField()),
|
||||
("type", models.TextField(blank=True, null=True)),
|
||||
(
|
||||
@ -469,6 +456,7 @@ class Migration(migrations.Migration):
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
db_column="uid",
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="transactions",
|
||||
@ -480,22 +468,350 @@ class Migration(migrations.Migration):
|
||||
"db_table": "transactions",
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="memberflag",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("member", "flag"), name="unique_member_flag"
|
||||
migrations.CreateModel(
|
||||
name="EventCategory",
|
||||
fields=[
|
||||
("id", models.IntegerField(primary_key=True, serialize=False)),
|
||||
("title", models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Event",
|
||||
fields=[
|
||||
(
|
||||
"eid",
|
||||
models.CharField(max_length=255, primary_key=True, serialize=False),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
("title", models.TextField()),
|
||||
("start", models.DateTimeField()),
|
||||
("end", models.DateTimeField(blank=True, null=True)),
|
||||
("cap", models.IntegerField(blank=True, null=True)),
|
||||
("count", models.IntegerField()),
|
||||
(
|
||||
"calendar",
|
||||
models.IntegerField(
|
||||
choices=[
|
||||
(0, "Hidden"),
|
||||
(1, "Green"),
|
||||
(2, "Red"),
|
||||
(3, "Yellow"),
|
||||
(4, "Blue"),
|
||||
(5, "Purple"),
|
||||
(6, "Magenta"),
|
||||
(7, "Grey"),
|
||||
(8, "Teal"),
|
||||
]
|
||||
),
|
||||
),
|
||||
("venue", models.TextField(blank=True, null=True)),
|
||||
(
|
||||
"category",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.eventcategory",
|
||||
),
|
||||
),
|
||||
(
|
||||
"occurred",
|
||||
models.GeneratedField(
|
||||
db_persist=True,
|
||||
expression=models.Q(
|
||||
("cap", 0),
|
||||
("count", 0),
|
||||
("calendar", 0),
|
||||
_connector="OR",
|
||||
_negated=True,
|
||||
),
|
||||
output_field=models.BooleanField(),
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventInstructor",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.TextField(blank=True)),
|
||||
(
|
||||
"member",
|
||||
models.OneToOneField(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.member",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventExt",
|
||||
fields=[
|
||||
(
|
||||
"event_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="membershipworks.event",
|
||||
),
|
||||
),
|
||||
(
|
||||
"materials_fee",
|
||||
models.DecimalField(
|
||||
blank=True, decimal_places=4, max_digits=13, null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"instructor",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.eventinstructor",
|
||||
),
|
||||
),
|
||||
(
|
||||
"instructor_flat_rate",
|
||||
models.DecimalField(decimal_places=4, default=0, max_digits=13),
|
||||
),
|
||||
(
|
||||
"instructor_percentage",
|
||||
models.DecimalField(decimal_places=4, default=0.5, max_digits=5),
|
||||
),
|
||||
("materials_fee_included_in_price", models.BooleanField(null=True)),
|
||||
("details", models.JSONField(blank=True, null=True)),
|
||||
("registrations", models.JSONField(blank=True, null=True)),
|
||||
(
|
||||
"details_timestamp",
|
||||
models.GeneratedField(
|
||||
db_persist=True,
|
||||
expression=models.Func(
|
||||
Cast(models.F("details___ts"), models.IntegerField()),
|
||||
function="to_timestamp",
|
||||
),
|
||||
output_field=models.DateTimeField(),
|
||||
verbose_name="Last details fetch",
|
||||
),
|
||||
),
|
||||
("should_survey", models.BooleanField(default=False)),
|
||||
("survey_email_sent", models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "event",
|
||||
"ordering": ["-start"],
|
||||
},
|
||||
bases=("membershipworks.event",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventMeetingTime",
|
||||
fields=[
|
||||
(
|
||||
"event",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="meeting_times",
|
||||
to="membershipworks.eventext",
|
||||
),
|
||||
),
|
||||
(
|
||||
"reservation_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="reservations.reservation",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"constraints": [],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventInvoice",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("date_submitted", models.DateField()),
|
||||
("date_paid", models.DateField(blank=True, null=True)),
|
||||
("pdf", models.FileField(upload_to="protected/invoices/%Y/%m/%d/")),
|
||||
("amount", models.DecimalField(decimal_places=4, max_digits=13)),
|
||||
(
|
||||
"event",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="invoice",
|
||||
to="membershipworks.eventext",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventTicketType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("label", models.TextField()),
|
||||
("restrict_to", models.TextField(blank=True, null=True)),
|
||||
("list_price", models.FloatField()),
|
||||
("quantity", models.IntegerField()),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
"base_manager_name": "objects",
|
||||
},
|
||||
),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
"SELECT\n row_number() over () as id,\n eventext.event_ptr_id as event_id,\n tkt.*,\n jsonb_path_query_first(\n eventext.details,\n '$.tkt[*] ? (exists (@.dsp ? (@[*] == \"5771675edcdf126302a2f6b9\"))).amt'\n )::numeric as members_price\n FROM membershipworks_eventext AS eventext,\n jsonb_to_recordset(eventext.details -> 'tkt') AS tkt (\n lbl TEXT,\n amt NUMERIC,\n cnt INT,\n dsp JSONB\n )",
|
||||
"membershipworks_eventtickettype",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"",
|
||||
"membershipworks_eventtickettype",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="member",
|
||||
index=models.Index(fields=["account_name"], name="account_name_idx"),
|
||||
migrations.CreateModel(
|
||||
name="EventAttendeeStats",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("gross_revenue", models.FloatField()),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="member",
|
||||
index=models.Index(fields=["first_name"], name="first_name_idx"),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
"SELECT eventext.event_ptr_id as event_id, SUM(usr.sum) as gross_revenue\n FROM\n membershipworks_eventext as eventext,\n jsonb_to_recordset(eventext.details -> 'usr') AS usr (\n sum NUMERIC\n )\n GROUP BY event_id",
|
||||
"membershipworks_eventattendeestats",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"",
|
||||
"membershipworks_eventattendeestats",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="member",
|
||||
index=models.Index(fields=["last_name"], name="last_name_idx"),
|
||||
migrations.CreateModel(
|
||||
name="EventAttendee",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=256)),
|
||||
("email", models.CharField(max_length=256)),
|
||||
("sum", models.FloatField()),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
"SELECT eventext.event_ptr_id as event_id, usr.*\n FROM\n membershipworks_eventext AS eventext,\n jsonb_to_recordset(eventext.details -> 'usr') AS usr (\n uid TEXT,\n nam TEXT,\n eml TEXT,\n sum NUMERIC\n )",
|
||||
"membershipworks_eventattendee",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"",
|
||||
"membershipworks_eventattendee",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventTicketAggregate",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("quantity", models.IntegerField()),
|
||||
("amount", models.DecimalField(decimal_places=4, max_digits=13)),
|
||||
("materials", models.DecimalField(decimal_places=4, max_digits=13)),
|
||||
(
|
||||
"amount_without_materials",
|
||||
models.DecimalField(decimal_places=4, max_digits=13),
|
||||
),
|
||||
(
|
||||
"instructor_revenue",
|
||||
models.DecimalField(decimal_places=4, max_digits=13),
|
||||
),
|
||||
(
|
||||
"instructor_amount",
|
||||
models.DecimalField(decimal_places=4, max_digits=13),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
'SELECT "membershipworks_eventtickettype"."event_id", SUM("membershipworks_eventtickettype"."cnt") AS "quantity", SUM((CASE WHEN ("membershipworks_eventtickettype"."dsp" ? (SELECT U0."id" FROM "flag" U0 WHERE (U0."name" = \'Members\' AND U0."type" = \'folder\') ORDER BY U0."name" ASC LIMIT 1) OR ("membershipworks_eventtickettype"."dsp" IS NULL AND ("membershipworks_event"."start" < \'2024-07-01 00:00:00-04:00\'::timestamptz OR "membershipworks_eventtickettype"."members_price" = 0))) THEN "membershipworks_eventtickettype"."amt" ELSE "membershipworks_eventtickettype"."members_price" END * "membershipworks_eventtickettype"."cnt")) AS "amount", SUM(CASE WHEN ("membershipworks_eventext"."materials_fee_included_in_price" OR ("membershipworks_eventext"."materials_fee" = 0 AND "membershipworks_eventext"."materials_fee" IS NOT NULL)) THEN ("membershipworks_eventext"."materials_fee" * "membershipworks_eventtickettype"."cnt") WHEN "membershipworks_eventext"."materials_fee_included_in_price" IS NULL THEN NULL ELSE 0 END) AS "materials", SUM(((CASE WHEN ("membershipworks_eventtickettype"."dsp" ? (SELECT U0."id" FROM "flag" U0 WHERE (U0."name" = \'Members\' AND U0."type" = \'folder\') ORDER BY U0."name" ASC LIMIT 1) OR ("membershipworks_eventtickettype"."dsp" IS NULL AND ("membershipworks_event"."start" < \'2024-07-01 00:00:00-04:00\'::timestamptz OR "membershipworks_eventtickettype"."members_price" = 0))) THEN "membershipworks_eventtickettype"."amt" ELSE "membershipworks_eventtickettype"."members_price" END * "membershipworks_eventtickettype"."cnt") - CASE WHEN ("membershipworks_eventext"."materials_fee_included_in_price" OR ("membershipworks_eventext"."materials_fee" = 0 AND "membershipworks_eventext"."materials_fee" IS NOT NULL)) THEN ("membershipworks_eventext"."materials_fee" * "membershipworks_eventtickettype"."cnt") WHEN "membershipworks_eventext"."materials_fee_included_in_price" IS NULL THEN NULL ELSE 0 END)) AS "amount_without_materials", SUM((((CASE WHEN ("membershipworks_eventtickettype"."dsp" ? (SELECT U0."id" FROM "flag" U0 WHERE (U0."name" = \'Members\' AND U0."type" = \'folder\') ORDER BY U0."name" ASC LIMIT 1) OR ("membershipworks_eventtickettype"."dsp" IS NULL AND ("membershipworks_event"."start" < \'2024-07-01 00:00:00-04:00\'::timestamptz OR "membershipworks_eventtickettype"."members_price" = 0))) THEN "membershipworks_eventtickettype"."amt" ELSE "membershipworks_eventtickettype"."members_price" END * "membershipworks_eventtickettype"."cnt") - CASE WHEN ("membershipworks_eventext"."materials_fee_included_in_price" OR ("membershipworks_eventext"."materials_fee" = 0 AND "membershipworks_eventext"."materials_fee" IS NOT NULL)) THEN ("membershipworks_eventext"."materials_fee" * "membershipworks_eventtickettype"."cnt") WHEN "membershipworks_eventext"."materials_fee_included_in_price" IS NULL THEN NULL ELSE 0 END) * "membershipworks_eventext"."instructor_percentage")) AS "instructor_revenue", SUM(((((CASE WHEN ("membershipworks_eventtickettype"."dsp" ? (SELECT U0."id" FROM "flag" U0 WHERE (U0."name" = \'Members\' AND U0."type" = \'folder\') ORDER BY U0."name" ASC LIMIT 1) OR ("membershipworks_eventtickettype"."dsp" IS NULL AND ("membershipworks_event"."start" < \'2024-07-01 00:00:00-04:00\'::timestamptz OR "membershipworks_eventtickettype"."members_price" = 0))) THEN "membershipworks_eventtickettype"."amt" ELSE "membershipworks_eventtickettype"."members_price" END * "membershipworks_eventtickettype"."cnt") - CASE WHEN ("membershipworks_eventext"."materials_fee_included_in_price" OR ("membershipworks_eventext"."materials_fee" = 0 AND "membershipworks_eventext"."materials_fee" IS NOT NULL)) THEN ("membershipworks_eventext"."materials_fee" * "membershipworks_eventtickettype"."cnt") WHEN "membershipworks_eventext"."materials_fee_included_in_price" IS NULL THEN NULL ELSE 0 END) * "membershipworks_eventext"."instructor_percentage") + CASE WHEN ("membershipworks_eventext"."materials_fee_included_in_price" OR ("membershipworks_eventext"."materials_fee" = 0 AND "membershipworks_eventext"."materials_fee" IS NOT NULL)) THEN ("membershipworks_eventext"."materials_fee" * "membershipworks_eventtickettype"."cnt") WHEN "membershipworks_eventext"."materials_fee_included_in_price" IS NULL THEN NULL ELSE 0 END)) AS "instructor_amount" FROM "membershipworks_eventtickettype" INNER JOIN "membershipworks_eventext" ON ("membershipworks_eventtickettype"."event_id" = "membershipworks_eventext"."event_ptr_id") INNER JOIN "membershipworks_event" ON ("membershipworks_eventext"."event_ptr_id" = "membershipworks_event"."eid") GROUP BY "membershipworks_eventtickettype"."event_id"',
|
||||
"membershipworks_eventticketaggregate",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"",
|
||||
"membershipworks_eventticketaggregate",
|
||||
engine="django.db.backends.postgresql",
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
]
|
||||
|
@ -1,36 +0,0 @@
|
||||
# Generated by Django 4.0.2 on 2022-03-01 19:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="member",
|
||||
name="accepted_covid19_policy",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="member",
|
||||
name="access_permitted_during_covid19_staffed_period_only",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="member",
|
||||
name="gift_membership_purchased_by",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="member",
|
||||
name="purchased_gift_membership_for",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="member",
|
||||
name="normal_access_permitted_during_covid19_limited_operations",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="member",
|
||||
name="self_certify_essential_business",
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.0 on 2023-12-20 06:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0002_remove_member_accepted_covid19_policy_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="transaction",
|
||||
name="sid",
|
||||
field=models.CharField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
]
|
@ -1,36 +0,0 @@
|
||||
# Generated by Django 5.0 on 2023-12-26 17:46
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0003_alter_transaction_sid"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="memberflag",
|
||||
name="member",
|
||||
field=models.ForeignKey(
|
||||
db_column="uid",
|
||||
db_constraint=False,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.member",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="transaction",
|
||||
name="member",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
db_column="uid",
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="transactions",
|
||||
to="membershipworks.member",
|
||||
),
|
||||
),
|
||||
]
|
@ -1,154 +0,0 @@
|
||||
# Generated by Django 5.0 on 2023-12-30 19:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0004_alter_memberflag_member_alter_transaction_member"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Event",
|
||||
fields=[
|
||||
(
|
||||
"eid",
|
||||
models.CharField(max_length=255, primary_key=True, serialize=False),
|
||||
),
|
||||
("url", models.TextField()),
|
||||
("title", models.TextField()),
|
||||
("start", models.DateTimeField()),
|
||||
("end", models.DateTimeField(blank=True, null=True)),
|
||||
("cap", models.IntegerField(blank=True, null=True)),
|
||||
("count", models.IntegerField()),
|
||||
(
|
||||
"calendar",
|
||||
models.IntegerField(
|
||||
choices=[
|
||||
(0, "Hidden"),
|
||||
(1, "Green"),
|
||||
(2, "Red"),
|
||||
(3, "Yellow"),
|
||||
(4, "Blue"),
|
||||
(5, "Purple"),
|
||||
(6, "Magenta"),
|
||||
(7, "Grey"),
|
||||
(8, "Teal"),
|
||||
]
|
||||
),
|
||||
),
|
||||
("venue", models.TextField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventCategory",
|
||||
fields=[
|
||||
("id", models.IntegerField(primary_key=True, serialize=False)),
|
||||
("title", models.TextField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventExt",
|
||||
fields=[
|
||||
(
|
||||
"event_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="membershipworks.event",
|
||||
),
|
||||
),
|
||||
(
|
||||
"materials_fee",
|
||||
models.DecimalField(
|
||||
blank=True, decimal_places=4, max_digits=13, null=True
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "event",
|
||||
},
|
||||
bases=("membershipworks.event",),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="event",
|
||||
name="category",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.eventcategory",
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventInstructor",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.TextField(blank=True)),
|
||||
(
|
||||
"member",
|
||||
models.OneToOneField(
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.member",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventMeetingTime",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("start", models.DateTimeField()),
|
||||
("end", models.DateTimeField()),
|
||||
(
|
||||
"event",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="meeting_times",
|
||||
to="membershipworks.eventext",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="instructor",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to="membershipworks.eventinstructor",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="eventmeetingtime",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("event", "start", "end"), name="unique_event_start_end"
|
||||
),
|
||||
),
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 5.0 on 2024-01-01 17:08
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
(
|
||||
"membershipworks",
|
||||
"0005_event_eventcategory_eventext_event_category_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="instructor_flat_rate",
|
||||
field=models.DecimalField(decimal_places=4, default=0, max_digits=13),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="instructor_percentage",
|
||||
field=models.DecimalField(decimal_places=4, default=0.5, max_digits=5),
|
||||
),
|
||||
]
|
@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-03 19:22
|
||||
|
||||
import django.db.models.expressions
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0006_eventext_instructor_flat_rate_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventmeetingtime",
|
||||
name="duration",
|
||||
field=models.GeneratedField(
|
||||
db_persist=False,
|
||||
expression=django.db.models.expressions.CombinedExpression(
|
||||
models.F("end"), "-", models.F("start")
|
||||
),
|
||||
output_field=models.DurationField(),
|
||||
),
|
||||
),
|
||||
]
|
@ -1,27 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-19 20:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0007_eventmeetingtime_duration"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="event",
|
||||
name="occurred",
|
||||
field=models.GeneratedField(
|
||||
db_persist=False,
|
||||
expression=models.Q(
|
||||
("cap", 0),
|
||||
("count", 0),
|
||||
("calendar", 0),
|
||||
_connector="OR",
|
||||
_negated=True,
|
||||
),
|
||||
output_field=models.BooleanField(),
|
||||
),
|
||||
),
|
||||
]
|
@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-25 02:43
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0008_event_occurred"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="materials_fee_included_in_price",
|
||||
field=models.BooleanField(null=True),
|
||||
),
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-29 19:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0009_eventext_materials_fee_included_in_price"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="eventext",
|
||||
options={"ordering": ["-start"], "verbose_name": "event"},
|
||||
),
|
||||
]
|
@ -1,29 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-29 19:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0010_alter_eventext_options"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="details",
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="details_timestamp",
|
||||
field=models.GeneratedField(
|
||||
db_persist=False,
|
||||
expression=models.Func(
|
||||
models.Func(models.F("details___ts"), function="FROM_UNIXTIME"),
|
||||
template="CONVERT_TZ(%(expressions)s, @@session.time_zone, 'UTC')",
|
||||
),
|
||||
output_field=models.DateTimeField(),
|
||||
),
|
||||
),
|
||||
]
|
@ -1,79 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-29 19:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import django_db_views.migration_functions
|
||||
import django_db_views.operations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0011_eventext_details"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EventAttendeeStats",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("gross_revenue", models.FloatField()),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
"SELECT\n row_number() over () as id,\n eventext.event_ptr_id AS event_id,\n tkt.label,\n tkt.list_price,\n tkt.quantity,\n GROUP_CONCAT(tkt.restrict_to SEPARATOR ',') as restrict_to\n FROM\n membershipworks_eventext AS eventext,\n JSON_TABLE (eventext.details, '$.tkt[*]' COLUMNS (\n id FOR ORDINALITY,\n label VARCHAR(256) PATH '$.lbl' ERROR ON ERROR,\n list_price DOUBLE PATH '$.amt' ERROR ON ERROR,\n quantity INTEGER PATH '$.cnt' DEFAULT 0 ON EMPTY ERROR ON ERROR,\n NESTED PATH '$.dsp[*]' COLUMNS (\n restrict_to VARCHAR(100) PATH '$' ERROR ON ERROR\n )\n )) AS tkt\n GROUP BY event_id, id",
|
||||
"membershipworks_eventtickettype",
|
||||
engine="django.db.backends.mysql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"", "membershipworks_eventtickettype", engine="django.db.backends.mysql"
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="EventTicketType",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("label", models.TextField()),
|
||||
("restrict_to", models.TextField(blank=True, null=True)),
|
||||
("list_price", models.FloatField()),
|
||||
("quantity", models.IntegerField()),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
"base_manager_name": "objects",
|
||||
},
|
||||
),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
"SELECT eventext.event_ptr_id as event_id, SUM(tkt.s) as gross_revenue\n FROM\n membershipworks_eventext as eventext,\n JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (\n s DOUBLE PATH '$.sum' DEFAULT 0 ON EMPTY\n )) as tkt\n GROUP BY event_id",
|
||||
"membershipworks_eventattendeestats",
|
||||
engine="django.db.backends.mysql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"",
|
||||
"membershipworks_eventattendeestats",
|
||||
engine="django.db.backends.mysql",
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
]
|
@ -1,46 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-02 22:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import django_db_views.migration_functions
|
||||
import django_db_views.operations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0012_eventattendeestats_eventtickettype"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EventAttendee",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=256)),
|
||||
("email", models.CharField(max_length=256)),
|
||||
("sum", models.FloatField()),
|
||||
],
|
||||
options={
|
||||
"managed": False,
|
||||
},
|
||||
),
|
||||
django_db_views.operations.ViewRunPython(
|
||||
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||
"SELECT eventext.event_ptr_id as event_id, tkt.uid, tkt.name, tkt.email, tkt.sum\n FROM\n membershipworks_eventext as eventext,\n JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (\n uid VARCHAR(24) PATH '$.uid',\n name VARCHAR(256) PATH '$.nam',\n email VARCHAR(256) PATH '$.eml',\n sum DOUBLE PATH '$.sum'\n )) as tkt",
|
||||
"membershipworks_eventattendee",
|
||||
engine="django.db.backends.mysql",
|
||||
),
|
||||
reverse_code=django_db_views.migration_functions.BackwardViewMigration(
|
||||
"", "membershipworks_eventattendee", engine="django.db.backends.mysql"
|
||||
),
|
||||
atomic=False,
|
||||
),
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-05 03:32
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0013_eventattendee"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="eventext",
|
||||
name="details_timestamp",
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-02-12 21:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0014_remove_eventext_details_timestamp"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddConstraint(
|
||||
model_name="eventmeetingtime",
|
||||
constraint=models.CheckConstraint(
|
||||
check=models.Q(("end__gt", models.F("start"))), name="end_after_start"
|
||||
),
|
||||
),
|
||||
]
|
@ -1,41 +0,0 @@
|
||||
# Generated by Django 5.0.2 on 2024-03-08 21:30
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0015_eventmeetingtime_end_after_start"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="EventInvoice",
|
||||
fields=[
|
||||
(
|
||||
"uuid",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("date_submitted", models.DateField()),
|
||||
("date_paid", models.DateField(blank=True, null=True)),
|
||||
("pdf", models.FileField(upload_to="invoices/%Y/%m/%d/")),
|
||||
("amount", models.DecimalField(decimal_places=4, max_digits=13)),
|
||||
(
|
||||
"event",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="invoice",
|
||||
to="membershipworks.eventext",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.0.4 on 2024-04-30 05:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0016_eventinvoice"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="registrations",
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="eventinvoice",
|
||||
name="pdf",
|
||||
field=models.FileField(upload_to="protected/invoices/%Y/%m/%d/"),
|
||||
),
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-08 16:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0017_eventext_registrations_alter_eventinvoice_pdf"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="details_timestamp",
|
||||
field=models.GeneratedField(
|
||||
db_persist=False,
|
||||
expression=models.Func(
|
||||
models.Func(models.F("details___ts"), function="FROM_UNIXTIME"),
|
||||
template="CONVERT_TZ(%(expressions)s, @@session.time_zone, 'UTC')",
|
||||
),
|
||||
output_field=models.DateTimeField(),
|
||||
verbose_name="Last details fetch",
|
||||
),
|
||||
),
|
||||
]
|
@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.0.6 on 2024-05-20 22:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0018_eventext_details_timestamp"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="should_survey",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="eventext",
|
||||
name="survey_email_sent",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
@ -1,82 +0,0 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-30 23:10
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def convert_meetingtimes_to_reservations(apps, schema_editor):
|
||||
Reservation = apps.get_model("reservations", "Reservation")
|
||||
EventMeetingTime = apps.get_model("membershipworks", "EventMeetingTime")
|
||||
for meeting_time in EventMeetingTime.objects.all():
|
||||
reservation = Reservation.objects.create(
|
||||
id=meeting_time.id,
|
||||
start=meeting_time.start,
|
||||
end=meeting_time.end,
|
||||
)
|
||||
meeting_time.reservation_ptr = reservation
|
||||
meeting_time.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("membershipworks", "0019_eventext_should_survey_eventext_survey_email_sent"),
|
||||
("reservations", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# add reservation field
|
||||
migrations.AddField(
|
||||
model_name="eventmeetingtime",
|
||||
name="reservation_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
serialize=False,
|
||||
to="reservations.reservation",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(convert_meetingtimes_to_reservations, atomic=True),
|
||||
# remove primary key
|
||||
migrations.RemoveField(
|
||||
model_name="eventmeetingtime",
|
||||
name="id",
|
||||
),
|
||||
# make reservation non-nullable
|
||||
migrations.AlterField(
|
||||
model_name="eventmeetingtime",
|
||||
name="reservation_ptr",
|
||||
field=models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="reservations.reservation",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
# delete old columns
|
||||
migrations.RemoveConstraint(
|
||||
model_name="eventmeetingtime",
|
||||
name="unique_event_start_end",
|
||||
),
|
||||
migrations.RemoveConstraint(
|
||||
model_name="eventmeetingtime",
|
||||
name="end_after_start",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="eventmeetingtime",
|
||||
name="duration",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="eventmeetingtime",
|
||||
name="end",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="eventmeetingtime",
|
||||
name="start",
|
||||
),
|
||||
]
|
@ -1,18 +1,18 @@
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING, TypedDict
|
||||
|
||||
import django.core.mail.message
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.db import models
|
||||
from django.db import connection, models
|
||||
from django.db.models import (
|
||||
Case,
|
||||
Count,
|
||||
Exists,
|
||||
ExpressionWrapper,
|
||||
F,
|
||||
Func,
|
||||
OuterRef,
|
||||
Q,
|
||||
QuerySet,
|
||||
@ -21,7 +21,7 @@ from django.db.models import (
|
||||
Value,
|
||||
When,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
@ -409,7 +409,7 @@ class Event(BaseModel):
|
||||
occurred = models.GeneratedField(
|
||||
expression=~(Q(cap=0) | Q(count=0) | Q(calendar=EventCalendar.HIDDEN)),
|
||||
output_field=models.BooleanField(),
|
||||
db_persist=False,
|
||||
db_persist=True,
|
||||
)
|
||||
# TODO:
|
||||
# "lgo": {
|
||||
@ -473,13 +473,7 @@ class EventExtQuerySet(models.QuerySet["EventExtAnnotated"]):
|
||||
def with_financials(self) -> "QuerySet[EventExtAnnotatedWithFinancials]":
|
||||
return self.annotate(
|
||||
**{
|
||||
field: Subquery(
|
||||
EventTicketType.objects.filter(event=OuterRef("pk"))
|
||||
.values("event__pk")
|
||||
.annotate(d=Sum(field))
|
||||
.values("d"),
|
||||
output_field=models.DecimalField(),
|
||||
)
|
||||
field: F(f"ticket_aggregates__{field}")
|
||||
for field in [
|
||||
"quantity",
|
||||
"amount",
|
||||
@ -492,11 +486,12 @@ class EventExtQuerySet(models.QuerySet["EventExtAnnotated"]):
|
||||
total_due_to_instructor=(
|
||||
F("instructor_amount") + F("instructor_flat_rate")
|
||||
),
|
||||
gross_revenue=Coalesce(F("attendee_stats__gross_revenue"), 0.0),
|
||||
net_revenue=ExpressionWrapper(
|
||||
F("gross_revenue") - F("total_due_to_instructor"),
|
||||
gross_revenue=Coalesce(
|
||||
F("attendee_stats__gross_revenue"),
|
||||
0,
|
||||
output_field=models.DecimalField(),
|
||||
),
|
||||
net_revenue=F("gross_revenue") - F("total_due_to_instructor"),
|
||||
)
|
||||
|
||||
|
||||
@ -549,12 +544,12 @@ class EventExt(Event):
|
||||
)
|
||||
details = models.JSONField(null=True, blank=True)
|
||||
details_timestamp = models.GeneratedField(
|
||||
expression=Func(
|
||||
Func(F("details___ts"), function="FROM_UNIXTIME"),
|
||||
template="CONVERT_TZ(%(expressions)s, @@session.time_zone, 'UTC')",
|
||||
expression=models.Func(
|
||||
Cast(models.F("details___ts"), models.IntegerField()),
|
||||
function="to_timestamp",
|
||||
),
|
||||
output_field=models.DateTimeField(),
|
||||
db_persist=False,
|
||||
db_persist=True,
|
||||
verbose_name="Last details fetch",
|
||||
)
|
||||
|
||||
@ -663,7 +658,7 @@ class EventInvoice(models.Model):
|
||||
|
||||
class EventTicketTypeQuerySet(models.QuerySet["EventTicketType"]):
|
||||
def group_by_ticket_type(self):
|
||||
return self.values("is_members_ticket").annotate(
|
||||
return self.values(is_members_ticket=Q(restrict_to__isnull=False)).annotate(
|
||||
label=Case(
|
||||
When(Q(is_members_ticket=True), Value("Members")),
|
||||
default=Value("Non-Members"),
|
||||
@ -685,17 +680,8 @@ class EventTicketTypeQuerySet(models.QuerySet["EventTicketType"]):
|
||||
|
||||
class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
||||
def get_queryset(self) -> models.QuerySet["EventTicketType"]:
|
||||
members_folder = Subquery(
|
||||
Flag.objects.filter(name="Members", type="folder").values("id")[:1]
|
||||
)
|
||||
qs = super().get_queryset()
|
||||
return qs.annotate(
|
||||
members_price=Subquery(
|
||||
qs.filter(event=OuterRef("event"), restrict_to=members_folder).values(
|
||||
"list_price"
|
||||
),
|
||||
output_field=models.FloatField(),
|
||||
),
|
||||
# Before 2024-07-01, use Members ticket price for any
|
||||
# restricted ticket, but list price for unrestricted
|
||||
# (Non-Members) ticket. After, use Members ticket price
|
||||
@ -703,7 +689,7 @@ class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
||||
actual_price=Case(
|
||||
When(
|
||||
# member ticket
|
||||
Q(restrict_to=members_folder)
|
||||
Q(restrict_to__has_key=settings.MW_MEMBERS_FOLDER_ID)
|
||||
| (
|
||||
# non-member ticket
|
||||
Q(restrict_to__isnull=True)
|
||||
@ -723,7 +709,6 @@ class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
||||
),
|
||||
default="members_price",
|
||||
),
|
||||
is_members_ticket=(Q(restrict_to__isnull=False)),
|
||||
materials=Case(
|
||||
When(
|
||||
(
|
||||
@ -764,37 +749,28 @@ class EventTicketType(DBView):
|
||||
event = models.ForeignKey(
|
||||
EventExt, on_delete=models.DO_NOTHING, related_name="ticket_types"
|
||||
)
|
||||
label = models.TextField()
|
||||
restrict_to = models.TextField(null=True, blank=True)
|
||||
list_price = models.FloatField()
|
||||
quantity = models.IntegerField()
|
||||
label = models.TextField(db_column="lbl")
|
||||
list_price = models.DecimalField(db_column="amt", max_digits=13, decimal_places=4)
|
||||
members_price = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
quantity = models.IntegerField(db_column="cnt")
|
||||
restrict_to = models.JSONField(db_column="dsp")
|
||||
|
||||
# Due to the presence of JSON_TABLE, this view must (as of MariaDB
|
||||
# 11.2.2) be created as the root user. See
|
||||
# https://jira.mariadb.org/browse/MDEV-27898
|
||||
|
||||
# nested path/group_concat to workaround inability to create JSON columns using
|
||||
# JSON_TABLE in views
|
||||
view_definition = """
|
||||
view_definition = f"""
|
||||
SELECT
|
||||
row_number() over () as id,
|
||||
eventext.event_ptr_id AS event_id,
|
||||
tkt.label,
|
||||
tkt.list_price,
|
||||
tkt.quantity,
|
||||
GROUP_CONCAT(tkt.restrict_to SEPARATOR ',') as restrict_to
|
||||
FROM
|
||||
membershipworks_eventext AS eventext,
|
||||
JSON_TABLE (eventext.details, '$.tkt[*]' COLUMNS (
|
||||
id FOR ORDINALITY,
|
||||
label VARCHAR(256) PATH '$.lbl' ERROR ON ERROR,
|
||||
list_price DOUBLE PATH '$.amt' ERROR ON ERROR,
|
||||
quantity INTEGER PATH '$.cnt' DEFAULT 0 ON EMPTY ERROR ON ERROR,
|
||||
NESTED PATH '$.dsp[*]' COLUMNS (
|
||||
restrict_to VARCHAR(100) PATH '$' ERROR ON ERROR
|
||||
)
|
||||
)) AS tkt
|
||||
GROUP BY event_id, id
|
||||
eventext.event_ptr_id as event_id,
|
||||
tkt.*,
|
||||
jsonb_path_query_first(
|
||||
eventext.details,
|
||||
'$.tkt[*] ? (exists (@.dsp ? (@[*] == "{settings.MW_MEMBERS_FOLDER_ID}"))).amt'
|
||||
)::numeric as members_price
|
||||
FROM membershipworks_eventext AS eventext,
|
||||
jsonb_to_recordset(eventext.details -> 'tkt') AS tkt (
|
||||
lbl TEXT,
|
||||
amt NUMERIC,
|
||||
cnt INT,
|
||||
dsp JSONB
|
||||
)
|
||||
"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -805,19 +781,59 @@ class EventTicketType(DBView):
|
||||
base_manager_name = "objects"
|
||||
|
||||
|
||||
class EventTicketAggregate(DBView):
|
||||
event = models.OneToOneField(
|
||||
EventExt,
|
||||
on_delete=models.DO_NOTHING,
|
||||
related_name="ticket_aggregates",
|
||||
primary_key=True,
|
||||
)
|
||||
quantity = models.IntegerField()
|
||||
amount = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
materials = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
amount_without_materials = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
instructor_revenue = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
instructor_amount = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
|
||||
@staticmethod
|
||||
def view_definition():
|
||||
qs = EventTicketType.objects.values("event").annotate(
|
||||
**{
|
||||
field: Sum(field)
|
||||
for field in [
|
||||
"quantity",
|
||||
"amount",
|
||||
"materials",
|
||||
"amount_without_materials",
|
||||
"instructor_revenue",
|
||||
"instructor_amount",
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
return cursor.mogrify(*qs.query.sql_with_params())
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.event}: {self.quantity}, {self.amount}"
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
|
||||
|
||||
class EventAttendeeStats(DBView):
|
||||
event = models.ForeignKey(
|
||||
EventExt, on_delete=models.DO_NOTHING, related_name="attendee_stats"
|
||||
)
|
||||
gross_revenue = models.FloatField()
|
||||
gross_revenue = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
|
||||
view_definition = """
|
||||
SELECT eventext.event_ptr_id as event_id, SUM(tkt.s) as gross_revenue
|
||||
SELECT eventext.event_ptr_id as event_id, SUM(usr.sum) as gross_revenue
|
||||
FROM
|
||||
membershipworks_eventext as eventext,
|
||||
JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (
|
||||
s DOUBLE PATH '$.sum' DEFAULT 0 ON EMPTY
|
||||
)) as tkt
|
||||
jsonb_to_recordset(eventext.details -> 'usr') AS usr (
|
||||
sum NUMERIC
|
||||
)
|
||||
GROUP BY event_id
|
||||
"""
|
||||
|
||||
@ -830,20 +846,20 @@ class EventAttendee(DBView):
|
||||
EventExt, on_delete=models.DO_NOTHING, related_name="attendees"
|
||||
)
|
||||
uid = models.ForeignKey(Member, on_delete=models.DO_NOTHING)
|
||||
name = models.CharField(max_length=256)
|
||||
email = models.CharField(max_length=256)
|
||||
sum = models.FloatField()
|
||||
name = models.CharField(max_length=256, db_column="nam")
|
||||
email = models.CharField(max_length=256, db_column="eml")
|
||||
sum = models.DecimalField(max_digits=13, decimal_places=4)
|
||||
|
||||
view_definition = """
|
||||
SELECT eventext.event_ptr_id as event_id, tkt.uid, tkt.name, tkt.email, tkt.sum
|
||||
SELECT eventext.event_ptr_id as event_id, usr.*
|
||||
FROM
|
||||
membershipworks_eventext as eventext,
|
||||
JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (
|
||||
uid VARCHAR(24) PATH '$.uid',
|
||||
name VARCHAR(256) PATH '$.nam',
|
||||
email VARCHAR(256) PATH '$.eml',
|
||||
sum DOUBLE PATH '$.sum'
|
||||
)) as tkt
|
||||
membershipworks_eventext AS eventext,
|
||||
jsonb_to_recordset(eventext.details -> 'usr') AS usr (
|
||||
uid TEXT,
|
||||
nam TEXT,
|
||||
eml TEXT,
|
||||
sum NUMERIC
|
||||
)
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
|
@ -137,6 +137,7 @@ def scrape_events():
|
||||
events = Event.objects.bulk_create(
|
||||
[Event.from_api_dict(event_data) for event_data in data["evt"]],
|
||||
update_conflicts=True,
|
||||
unique_fields=["eid"],
|
||||
update_fields=[
|
||||
field.attname
|
||||
for field in Event._meta.get_fields()
|
||||
|
@ -10,6 +10,7 @@ from django.contrib.auth.mixins import (
|
||||
AccessMixin,
|
||||
PermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.postgres.aggregates import StringAgg
|
||||
from django.core import mail
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db.models import OuterRef, Q, Subquery
|
||||
@ -33,7 +34,6 @@ import django_tables2 as tables
|
||||
import weasyprint
|
||||
from dal import autocomplete
|
||||
from django_filters.views import BaseFilterView
|
||||
from django_mysql.models.aggregates import GroupConcat
|
||||
from django_sendfile import sendfile
|
||||
from django_tables2 import A, SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
@ -538,7 +538,7 @@ class MissingPaperworkReport(
|
||||
membership=Subquery(
|
||||
qs.filter(
|
||||
pk=OuterRef("pk"), flags__type__in=("level", "addon")
|
||||
).values(m=GroupConcat("flags__name"))
|
||||
).values(m=StringAgg("flags__name", ", "))
|
||||
),
|
||||
)
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.contrib.postgres.aggregates import StringAgg
|
||||
from django.contrib.staticfiles import finders as staticfiles_finders
|
||||
from django.db import models
|
||||
from django.db.models import (
|
||||
@ -16,14 +17,13 @@ from django.db.models import (
|
||||
Value,
|
||||
When,
|
||||
)
|
||||
from django.db.models.functions import Concat
|
||||
from django.db.models.functions import Cast, Concat
|
||||
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.views.generic import ListView
|
||||
|
||||
import requests
|
||||
import weasyprint
|
||||
from django_mysql.models.aggregates import GroupConcat
|
||||
from django_tables2 import SingleTableMixin
|
||||
from django_tables2.export.views import ExportMixin
|
||||
|
||||
@ -158,14 +158,16 @@ class InstructorOrVendorReport(
|
||||
.get_table_data()
|
||||
.values("name")
|
||||
.annotate(
|
||||
instructor_agreement_date=GroupConcat(
|
||||
"instructor_agreement_date", distinct=True, ordering="asc"
|
||||
instructor_agreement_date=StringAgg(
|
||||
Cast("instructor_agreement_date", models.TextField()),
|
||||
delimiter=", ",
|
||||
distinct=True,
|
||||
),
|
||||
w9_date=GroupConcat("w9_date", distinct=True, ordering="asc"),
|
||||
phone=GroupConcat("phone", distinct=True, ordering="asc"),
|
||||
email_address=GroupConcat(
|
||||
"email_address", distinct=True, ordering="asc"
|
||||
w9_date=StringAgg(
|
||||
Cast("w9_date", models.TextField()), ", ", distinct=True
|
||||
),
|
||||
phone=StringAgg("phone", ", ", distinct=True),
|
||||
email_address=StringAgg("email_address", ", ", distinct=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
89
pdm.lock
generated
89
pdm.lock
generated
@ -5,7 +5,7 @@
|
||||
groups = ["default", "debug", "dev", "lint", "server", "typing"]
|
||||
strategy = ["inherit_metadata"]
|
||||
lock_version = "4.5.0"
|
||||
content_hash = "sha256:4a7538bb6a4aabea5a3641a9f69edf6045587a129895ddf5108bf765141fbe6f"
|
||||
content_hash = "sha256:0c560e1f2a81810e95ba8993538337bfc8b39576b6923280cfbfad91ad5b58f2"
|
||||
|
||||
[[metadata.targets]]
|
||||
requires_python = "==3.11.*"
|
||||
@ -561,21 +561,6 @@ files = [
|
||||
{file = "django_model_utils-4.5.1.tar.gz", hash = "sha256:1220f22d9a467d53a1e0f4cda4857df0b2f757edf9a29955c42461988caa648a"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-mysql"
|
||||
version = "4.14.0"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Django-MySQL extends Django's built-in MySQL and MariaDB support their specific features not available on other databases."
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"django>=3.2",
|
||||
]
|
||||
files = [
|
||||
{file = "django_mysql-4.14.0-py3-none-any.whl", hash = "sha256:c8ae4b8004bd2e1b74999f0254d255771043913273216a8514cf09aa4bd937bb"},
|
||||
{file = "django_mysql-4.14.0.tar.gz", hash = "sha256:77cb615afb8f2a92636617d46dbe11b97b28e2b97d8373cf7752c3e1f2c619f1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-nh3"
|
||||
version = "0.1.1"
|
||||
@ -1439,17 +1424,6 @@ files = [
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mysqlclient"
|
||||
version = "2.2.4"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Python interface to MySQL"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "mysqlclient-2.2.4.tar.gz", hash = "sha256:33bc9fb3464e7d7c10b1eaf7336c5ff8f2a3d3b88bab432116ad2490beb3bf41"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nh3"
|
||||
version = "0.2.18"
|
||||
@ -1624,6 +1598,67 @@ files = [
|
||||
{file = "protobuf-5.27.3.tar.gz", hash = "sha256:82460903e640f2b7e34ee81a947fdaad89de796d324bcbc38ff5430bcdead82c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "PostgreSQL database adapter for Python"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"backports-zoneinfo>=0.2.0; python_version < \"3.9\"",
|
||||
"typing-extensions>=4.4",
|
||||
"tzdata; sys_platform == \"win32\"",
|
||||
]
|
||||
files = [
|
||||
{file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"},
|
||||
{file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-binary"
|
||||
version = "3.2.1"
|
||||
requires_python = ">=3.8"
|
||||
summary = "PostgreSQL database adapter for Python -- C optimisation distribution"
|
||||
groups = ["default"]
|
||||
marker = "implementation_name != \"pypy\" and python_version == \"3.11\""
|
||||
files = [
|
||||
{file = "psycopg_binary-3.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa3931f308ab4a479d0ee22dc04bea867a6365cac0172e5ddcba359da043854b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg-pool"
|
||||
version = "3.2.2"
|
||||
requires_python = ">=3.8"
|
||||
summary = "Connection Pool for Psycopg"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"typing-extensions>=4.4",
|
||||
]
|
||||
files = [
|
||||
{file = "psycopg_pool-3.2.2-py3-none-any.whl", hash = "sha256:273081d0fbfaced4f35e69200c89cb8fbddfe277c38cc86c235b90a2ec2c8153"},
|
||||
{file = "psycopg_pool-3.2.2.tar.gz", hash = "sha256:9e22c370045f6d7f2666a5ad1b0caf345f9f1912195b0b25d0d3bcc4f3a7389c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psycopg"
|
||||
version = "3.2.1"
|
||||
extras = ["binary", "pool"]
|
||||
requires_python = ">=3.8"
|
||||
summary = "PostgreSQL database adapter for Python"
|
||||
groups = ["default"]
|
||||
marker = "python_version == \"3.11\""
|
||||
dependencies = [
|
||||
"psycopg-binary==3.2.1; implementation_name != \"pypy\"",
|
||||
"psycopg-pool",
|
||||
"psycopg==3.2.1",
|
||||
]
|
||||
files = [
|
||||
{file = "psycopg-3.2.1-py3-none-any.whl", hash = "sha256:ece385fb413a37db332f97c49208b36cf030ff02b199d7635ed2fbd378724175"},
|
||||
{file = "psycopg-3.2.1.tar.gz", hash = "sha256:dc8da6dc8729dacacda3cc2f17d2c9397a70a66cf0d2b69c91065d60d5f00cb7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
|
@ -16,7 +16,6 @@ dependencies = [
|
||||
"markdownify~=0.13",
|
||||
"mdformat~=0.7",
|
||||
"mdformat-tables~=0.4",
|
||||
"mysqlclient~=2.2",
|
||||
"django-autocomplete-light~=3.11",
|
||||
"weasyprint~=62.3",
|
||||
"requests~=2.32",
|
||||
@ -34,7 +33,6 @@ dependencies = [
|
||||
"tablib[ods,xlsx]~=3.6",
|
||||
"django-filter~=24.3",
|
||||
"django-db-views~=0.1",
|
||||
"django-mysql~=4.14",
|
||||
"django-weasyprint~=2.3",
|
||||
"django-sendfile2~=0.7",
|
||||
"django-bootstrap5~=24.2",
|
||||
@ -44,6 +42,7 @@ dependencies = [
|
||||
"google-api-python-client~=2.142",
|
||||
"google-auth-oauthlib~=1.2",
|
||||
"django-model-utils~=4.5",
|
||||
"psycopg[binary,pool]~=3.2",
|
||||
]
|
||||
requires-python = ">=3.11"
|
||||
|
||||
|
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
||||
(
|
||||
"duration",
|
||||
models.GeneratedField(
|
||||
db_persist=False,
|
||||
db_persist=True,
|
||||
expression=django.db.models.expressions.CombinedExpression(
|
||||
models.F("end"), "-", models.F("start")
|
||||
),
|
||||
|
@ -87,7 +87,7 @@ class Reservation(models.Model):
|
||||
duration = models.GeneratedField(
|
||||
expression=F("end") - F("start"),
|
||||
output_field=models.DurationField(),
|
||||
db_persist=False,
|
||||
db_persist=True,
|
||||
)
|
||||
|
||||
objects = ReservationQuerySet.as_manager()
|
||||
|
Loading…
Reference in New Issue
Block a user