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
|
runs-on: ubuntu-latest
|
||||||
container: catthehacker/ubuntu:act-latest
|
container: catthehacker/ubuntu:act-latest
|
||||||
services:
|
services:
|
||||||
mariadb:
|
postgres:
|
||||||
# TODO: this is pinned to avoid what apears to be a bug with
|
image: postgres:15
|
||||||
# MariaDB >= 10.11.9, and collation issues with 11.x.x
|
|
||||||
image: mariadb:10.11.8
|
|
||||||
env:
|
env:
|
||||||
MARIADB_ROOT_PASSWORD: whatever
|
POSTGRES_PASSWORD: whatever
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test: ["CMD-SHELL", "pg_isready"]
|
||||||
[
|
|
||||||
"CMD",
|
|
||||||
"healthcheck.sh",
|
|
||||||
"--su-mysql",
|
|
||||||
"--connect",
|
|
||||||
"--innodb_initialized",
|
|
||||||
]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Setup PDM
|
- name: Setup PDM
|
||||||
@ -35,7 +26,7 @@ jobs:
|
|||||||
- name: Install apt dependencies
|
- name: Install apt dependencies
|
||||||
run: >-
|
run: >-
|
||||||
sudo apt-get update &&
|
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
|
- name: Install python dependencies
|
||||||
run: pdm sync -d -G dev
|
run: pdm sync -d -G dev
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ class Base(Configuration):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setup(cls):
|
def setup(cls):
|
||||||
super().setup()
|
super().setup()
|
||||||
cls.DATABASES["default"]["OPTIONS"] = {"charset": "utf8mb4"}
|
cls.DATABASES["default"]["OPTIONS"] = {"pool": True}
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
"dal",
|
"dal",
|
||||||
@ -52,7 +52,6 @@ class Base(Configuration):
|
|||||||
"django_tables2",
|
"django_tables2",
|
||||||
"django_filters",
|
"django_filters",
|
||||||
"django_db_views",
|
"django_db_views",
|
||||||
"django_mysql",
|
|
||||||
"django_sendfile",
|
"django_sendfile",
|
||||||
"django_bootstrap5",
|
"django_bootstrap5",
|
||||||
# "tasks.apps.TasksConfig",
|
# "tasks.apps.TasksConfig",
|
||||||
@ -106,9 +105,6 @@ class Base(Configuration):
|
|||||||
|
|
||||||
WSGI_APPLICATION = "cmsmanage.wsgi.application"
|
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
|
# Default URL to redirect to after authentication
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
LOGIN_URL = "/auth/login/"
|
LOGIN_URL = "/auth/login/"
|
||||||
@ -213,6 +209,9 @@ class Base(Configuration):
|
|||||||
# CMSManage specific stuff
|
# CMSManage specific stuff
|
||||||
WIKI_URL = values.URLValue("https://wiki.claremontmakerspace.org")
|
WIKI_URL = values.URLValue("https://wiki.claremontmakerspace.org")
|
||||||
|
|
||||||
|
# ID of flag for Members folder in MembershipWorks
|
||||||
|
MW_MEMBERS_FOLDER_ID = "5771675edcdf126302a2f6b9"
|
||||||
|
|
||||||
|
|
||||||
class NonCIBase(Base):
|
class NonCIBase(Base):
|
||||||
"""required for all but CI"""
|
"""required for all but CI"""
|
||||||
@ -367,13 +366,10 @@ class CI(Base):
|
|||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.mysql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"HOST": "mariadb",
|
"HOST": "postgres",
|
||||||
"NAME": "CMS_Database",
|
"NAME": "cms",
|
||||||
"USER": "root",
|
"USER": "postgres",
|
||||||
"PASSWORD": "whatever",
|
"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
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
("membershipworks", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
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(
|
migrations.CreateModel(
|
||||||
name="HIDEvent",
|
name="HIDEvent",
|
||||||
fields=[
|
fields=[
|
||||||
@ -21,7 +47,6 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="ID",
|
verbose_name="ID",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("door_name", models.CharField(db_column="doorName", max_length=64)),
|
|
||||||
("timestamp", models.DateTimeField()),
|
("timestamp", models.DateTimeField()),
|
||||||
(
|
(
|
||||||
"event_type",
|
"event_type",
|
||||||
@ -88,16 +113,173 @@ class Migration(migrations.Migration):
|
|||||||
blank=True, db_column="rawCardNumber", max_length=8, null=True
|
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={
|
options={
|
||||||
"db_table": "hidevent",
|
"db_table": "hidevent",
|
||||||
"ordering": ("-timestamp",),
|
"ordering": ("-timestamp",),
|
||||||
|
"constraints": [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=("door", "timestamp", "event_type"),
|
||||||
|
name="unique_hidevent",
|
||||||
|
)
|
||||||
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddConstraint(
|
migrations.CreateModel(
|
||||||
model_name="hidevent",
|
name="DoorCardholderMember",
|
||||||
constraint=models.UniqueConstraint(
|
fields=[
|
||||||
fields=("door_name", "timestamp", "event_type"), name="unique_hidevent"
|
(
|
||||||
),
|
"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(),
|
output_field=models.BooleanField(),
|
||||||
db_persist=False,
|
db_persist=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = HIDEventQuerySet.as_manager()
|
objects = HIDEventQuerySet.as_manager()
|
||||||
|
@ -17,13 +17,15 @@ def get_cardholders(door: Door):
|
|||||||
member_id=cardholder.attrib.get("custom2"),
|
member_id=cardholder.attrib.get("custom2"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cardholders = door.controller.get_cardholders()
|
||||||
DoorCardholderMember.objects.bulk_create(
|
DoorCardholderMember.objects.bulk_create(
|
||||||
(
|
(
|
||||||
make_ch_member(cardholder)
|
make_ch_member(cardholder)
|
||||||
for cardholder in door.controller.get_cardholders()
|
for cardholder in cardholders
|
||||||
if "custom2" in cardholder.attrib
|
if "custom2" in cardholder.attrib
|
||||||
),
|
),
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
|
unique_fields=("door", "cardholder_id"),
|
||||||
update_fields=("member",),
|
update_fields=("member",),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@ import datetime
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
|
from django.contrib.postgres.aggregates import StringAgg
|
||||||
from django.core.exceptions import BadRequest
|
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.db.models.functions import Lead, Trunc
|
||||||
from django.urls import path, reverse_lazy
|
from django.urls import path, reverse_lazy
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
@ -12,8 +13,6 @@ from django.views.generic.list import ListView
|
|||||||
import django_filters
|
import django_filters
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django_filters.views import BaseFilterView
|
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 import SingleTableMixin
|
||||||
from django_tables2.export.views import ExportMixin
|
from django_tables2.export.views import ExportMixin
|
||||||
|
|
||||||
@ -223,8 +222,10 @@ class MostActiveMembers(BaseAccessReport):
|
|||||||
.values("member_id")
|
.values("member_id")
|
||||||
.annotate(
|
.annotate(
|
||||||
access_count=Count("member_id"),
|
access_count=Count("member_id"),
|
||||||
name=GroupConcat(
|
name=StringAgg(
|
||||||
ConcatWS("forename", "surname", separator=" "), distinct=True
|
Func(Value(" "), "forename", "surname", function="concat_ws"),
|
||||||
|
", ",
|
||||||
|
distinct=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.order_by("-access_count")
|
.order_by("-access_count")
|
||||||
@ -249,8 +250,10 @@ class DetailByDay(BaseAccessReport):
|
|||||||
"member_id",
|
"member_id",
|
||||||
filter=Q(event_type__in=HIDEvent.EventType.any_granted_access()),
|
filter=Q(event_type__in=HIDEvent.EventType.any_granted_access()),
|
||||||
),
|
),
|
||||||
name=GroupConcat(
|
name=StringAgg(
|
||||||
ConcatWS("forename", "surname", separator=" "), distinct=True
|
Func(Value(" "), "forename", "surname", function="concat_ws"),
|
||||||
|
", ",
|
||||||
|
distinct=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.order_by("-timestamp__date")
|
.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
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
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):
|
class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = []
|
dependencies = [
|
||||||
|
("reservations", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@ -115,18 +123,6 @@ class Migration(migrations.Migration):
|
|||||||
blank=True, db_column="Parent Account ID", null=True
|
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",
|
"closet_storage",
|
||||||
models.TextField(
|
models.TextField(
|
||||||
@ -151,18 +147,6 @@ class Migration(migrations.Migration):
|
|||||||
db_column="Access Permitted Shops During Extended Hours?"
|
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",
|
"access_front_door_and_studio_space_during_extended_hours",
|
||||||
models.BooleanField(
|
models.BooleanField(
|
||||||
@ -349,18 +333,15 @@ class Migration(migrations.Migration):
|
|||||||
"liability_form_filled_out",
|
"liability_form_filled_out",
|
||||||
models.BooleanField(db_column="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={
|
options={
|
||||||
"db_table": "members",
|
"db_table": "members",
|
||||||
"ordering": ("first_name", "last_name"),
|
"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(
|
migrations.CreateModel(
|
||||||
@ -386,6 +367,7 @@ class Migration(migrations.Migration):
|
|||||||
"member",
|
"member",
|
||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
db_column="uid",
|
db_column="uid",
|
||||||
|
db_constraint=False,
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
to="membershipworks.member",
|
to="membershipworks.member",
|
||||||
),
|
),
|
||||||
@ -393,6 +375,11 @@ class Migration(migrations.Migration):
|
|||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
"db_table": "memberflag",
|
"db_table": "memberflag",
|
||||||
|
"constraints": [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=("member", "flag"), name="unique_member_flag"
|
||||||
|
)
|
||||||
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
@ -416,7 +403,7 @@ class Migration(migrations.Migration):
|
|||||||
verbose_name="ID",
|
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()),
|
("timestamp", models.DateTimeField()),
|
||||||
("type", models.TextField(blank=True, null=True)),
|
("type", models.TextField(blank=True, null=True)),
|
||||||
(
|
(
|
||||||
@ -469,6 +456,7 @@ class Migration(migrations.Migration):
|
|||||||
models.ForeignKey(
|
models.ForeignKey(
|
||||||
blank=True,
|
blank=True,
|
||||||
db_column="uid",
|
db_column="uid",
|
||||||
|
db_constraint=False,
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
related_name="transactions",
|
related_name="transactions",
|
||||||
@ -480,22 +468,350 @@ class Migration(migrations.Migration):
|
|||||||
"db_table": "transactions",
|
"db_table": "transactions",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddConstraint(
|
migrations.CreateModel(
|
||||||
model_name="memberflag",
|
name="EventCategory",
|
||||||
constraint=models.UniqueConstraint(
|
fields=[
|
||||||
fields=("member", "flag"), name="unique_member_flag"
|
("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(
|
migrations.CreateModel(
|
||||||
model_name="member",
|
name="EventAttendeeStats",
|
||||||
index=models.Index(fields=["account_name"], name="account_name_idx"),
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("gross_revenue", models.FloatField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"managed": False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
django_db_views.operations.ViewRunPython(
|
||||||
model_name="member",
|
code=django_db_views.migration_functions.ForwardViewMigration(
|
||||||
index=models.Index(fields=["first_name"], name="first_name_idx"),
|
"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(
|
migrations.CreateModel(
|
||||||
model_name="member",
|
name="EventAttendee",
|
||||||
index=models.Index(fields=["last_name"], name="last_name_idx"),
|
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
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from decimal import Decimal
|
||||||
from typing import TYPE_CHECKING, TypedDict
|
from typing import TYPE_CHECKING, TypedDict
|
||||||
|
|
||||||
import django.core.mail.message
|
import django.core.mail.message
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractBaseUser
|
from django.contrib.auth.models import AbstractBaseUser
|
||||||
from django.db import models
|
from django.db import connection, models
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Case,
|
Case,
|
||||||
Count,
|
Count,
|
||||||
Exists,
|
Exists,
|
||||||
ExpressionWrapper,
|
ExpressionWrapper,
|
||||||
F,
|
F,
|
||||||
Func,
|
|
||||||
OuterRef,
|
OuterRef,
|
||||||
Q,
|
Q,
|
||||||
QuerySet,
|
QuerySet,
|
||||||
@ -21,7 +21,7 @@ from django.db.models import (
|
|||||||
Value,
|
Value,
|
||||||
When,
|
When,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Cast, Coalesce
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
@ -409,7 +409,7 @@ class Event(BaseModel):
|
|||||||
occurred = models.GeneratedField(
|
occurred = models.GeneratedField(
|
||||||
expression=~(Q(cap=0) | Q(count=0) | Q(calendar=EventCalendar.HIDDEN)),
|
expression=~(Q(cap=0) | Q(count=0) | Q(calendar=EventCalendar.HIDDEN)),
|
||||||
output_field=models.BooleanField(),
|
output_field=models.BooleanField(),
|
||||||
db_persist=False,
|
db_persist=True,
|
||||||
)
|
)
|
||||||
# TODO:
|
# TODO:
|
||||||
# "lgo": {
|
# "lgo": {
|
||||||
@ -473,13 +473,7 @@ class EventExtQuerySet(models.QuerySet["EventExtAnnotated"]):
|
|||||||
def with_financials(self) -> "QuerySet[EventExtAnnotatedWithFinancials]":
|
def with_financials(self) -> "QuerySet[EventExtAnnotatedWithFinancials]":
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
**{
|
**{
|
||||||
field: Subquery(
|
field: F(f"ticket_aggregates__{field}")
|
||||||
EventTicketType.objects.filter(event=OuterRef("pk"))
|
|
||||||
.values("event__pk")
|
|
||||||
.annotate(d=Sum(field))
|
|
||||||
.values("d"),
|
|
||||||
output_field=models.DecimalField(),
|
|
||||||
)
|
|
||||||
for field in [
|
for field in [
|
||||||
"quantity",
|
"quantity",
|
||||||
"amount",
|
"amount",
|
||||||
@ -492,11 +486,12 @@ class EventExtQuerySet(models.QuerySet["EventExtAnnotated"]):
|
|||||||
total_due_to_instructor=(
|
total_due_to_instructor=(
|
||||||
F("instructor_amount") + F("instructor_flat_rate")
|
F("instructor_amount") + F("instructor_flat_rate")
|
||||||
),
|
),
|
||||||
gross_revenue=Coalesce(F("attendee_stats__gross_revenue"), 0.0),
|
gross_revenue=Coalesce(
|
||||||
net_revenue=ExpressionWrapper(
|
F("attendee_stats__gross_revenue"),
|
||||||
F("gross_revenue") - F("total_due_to_instructor"),
|
0,
|
||||||
output_field=models.DecimalField(),
|
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 = models.JSONField(null=True, blank=True)
|
||||||
details_timestamp = models.GeneratedField(
|
details_timestamp = models.GeneratedField(
|
||||||
expression=Func(
|
expression=models.Func(
|
||||||
Func(F("details___ts"), function="FROM_UNIXTIME"),
|
Cast(models.F("details___ts"), models.IntegerField()),
|
||||||
template="CONVERT_TZ(%(expressions)s, @@session.time_zone, 'UTC')",
|
function="to_timestamp",
|
||||||
),
|
),
|
||||||
output_field=models.DateTimeField(),
|
output_field=models.DateTimeField(),
|
||||||
db_persist=False,
|
db_persist=True,
|
||||||
verbose_name="Last details fetch",
|
verbose_name="Last details fetch",
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -663,7 +658,7 @@ class EventInvoice(models.Model):
|
|||||||
|
|
||||||
class EventTicketTypeQuerySet(models.QuerySet["EventTicketType"]):
|
class EventTicketTypeQuerySet(models.QuerySet["EventTicketType"]):
|
||||||
def group_by_ticket_type(self):
|
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(
|
label=Case(
|
||||||
When(Q(is_members_ticket=True), Value("Members")),
|
When(Q(is_members_ticket=True), Value("Members")),
|
||||||
default=Value("Non-Members"),
|
default=Value("Non-Members"),
|
||||||
@ -685,17 +680,8 @@ class EventTicketTypeQuerySet(models.QuerySet["EventTicketType"]):
|
|||||||
|
|
||||||
class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
||||||
def get_queryset(self) -> models.QuerySet["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()
|
qs = super().get_queryset()
|
||||||
return qs.annotate(
|
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
|
# Before 2024-07-01, use Members ticket price for any
|
||||||
# restricted ticket, but list price for unrestricted
|
# restricted ticket, but list price for unrestricted
|
||||||
# (Non-Members) ticket. After, use Members ticket price
|
# (Non-Members) ticket. After, use Members ticket price
|
||||||
@ -703,7 +689,7 @@ class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
|||||||
actual_price=Case(
|
actual_price=Case(
|
||||||
When(
|
When(
|
||||||
# member ticket
|
# member ticket
|
||||||
Q(restrict_to=members_folder)
|
Q(restrict_to__has_key=settings.MW_MEMBERS_FOLDER_ID)
|
||||||
| (
|
| (
|
||||||
# non-member ticket
|
# non-member ticket
|
||||||
Q(restrict_to__isnull=True)
|
Q(restrict_to__isnull=True)
|
||||||
@ -723,7 +709,6 @@ class EventTicketTypeManager(models.Manager["EventTicketType"]):
|
|||||||
),
|
),
|
||||||
default="members_price",
|
default="members_price",
|
||||||
),
|
),
|
||||||
is_members_ticket=(Q(restrict_to__isnull=False)),
|
|
||||||
materials=Case(
|
materials=Case(
|
||||||
When(
|
When(
|
||||||
(
|
(
|
||||||
@ -764,37 +749,28 @@ class EventTicketType(DBView):
|
|||||||
event = models.ForeignKey(
|
event = models.ForeignKey(
|
||||||
EventExt, on_delete=models.DO_NOTHING, related_name="ticket_types"
|
EventExt, on_delete=models.DO_NOTHING, related_name="ticket_types"
|
||||||
)
|
)
|
||||||
label = models.TextField()
|
label = models.TextField(db_column="lbl")
|
||||||
restrict_to = models.TextField(null=True, blank=True)
|
list_price = models.DecimalField(db_column="amt", max_digits=13, decimal_places=4)
|
||||||
list_price = models.FloatField()
|
members_price = models.DecimalField(max_digits=13, decimal_places=4)
|
||||||
quantity = models.IntegerField()
|
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
|
view_definition = f"""
|
||||||
# 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 = """
|
|
||||||
SELECT
|
SELECT
|
||||||
row_number() over () as id,
|
row_number() over () as id,
|
||||||
eventext.event_ptr_id AS event_id,
|
eventext.event_ptr_id as event_id,
|
||||||
tkt.label,
|
tkt.*,
|
||||||
tkt.list_price,
|
jsonb_path_query_first(
|
||||||
tkt.quantity,
|
eventext.details,
|
||||||
GROUP_CONCAT(tkt.restrict_to SEPARATOR ',') as restrict_to
|
'$.tkt[*] ? (exists (@.dsp ? (@[*] == "{settings.MW_MEMBERS_FOLDER_ID}"))).amt'
|
||||||
FROM
|
)::numeric as members_price
|
||||||
membershipworks_eventext AS eventext,
|
FROM membershipworks_eventext AS eventext,
|
||||||
JSON_TABLE (eventext.details, '$.tkt[*]' COLUMNS (
|
jsonb_to_recordset(eventext.details -> 'tkt') AS tkt (
|
||||||
id FOR ORDINALITY,
|
lbl TEXT,
|
||||||
label VARCHAR(256) PATH '$.lbl' ERROR ON ERROR,
|
amt NUMERIC,
|
||||||
list_price DOUBLE PATH '$.amt' ERROR ON ERROR,
|
cnt INT,
|
||||||
quantity INTEGER PATH '$.cnt' DEFAULT 0 ON EMPTY ERROR ON ERROR,
|
dsp JSONB
|
||||||
NESTED PATH '$.dsp[*]' COLUMNS (
|
)
|
||||||
restrict_to VARCHAR(100) PATH '$' ERROR ON ERROR
|
|
||||||
)
|
|
||||||
)) AS tkt
|
|
||||||
GROUP BY event_id, id
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@ -805,19 +781,59 @@ class EventTicketType(DBView):
|
|||||||
base_manager_name = "objects"
|
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):
|
class EventAttendeeStats(DBView):
|
||||||
event = models.ForeignKey(
|
event = models.ForeignKey(
|
||||||
EventExt, on_delete=models.DO_NOTHING, related_name="attendee_stats"
|
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 = """
|
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
|
FROM
|
||||||
membershipworks_eventext as eventext,
|
membershipworks_eventext as eventext,
|
||||||
JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (
|
jsonb_to_recordset(eventext.details -> 'usr') AS usr (
|
||||||
s DOUBLE PATH '$.sum' DEFAULT 0 ON EMPTY
|
sum NUMERIC
|
||||||
)) as tkt
|
)
|
||||||
GROUP BY event_id
|
GROUP BY event_id
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -830,20 +846,20 @@ class EventAttendee(DBView):
|
|||||||
EventExt, on_delete=models.DO_NOTHING, related_name="attendees"
|
EventExt, on_delete=models.DO_NOTHING, related_name="attendees"
|
||||||
)
|
)
|
||||||
uid = models.ForeignKey(Member, on_delete=models.DO_NOTHING)
|
uid = models.ForeignKey(Member, on_delete=models.DO_NOTHING)
|
||||||
name = models.CharField(max_length=256)
|
name = models.CharField(max_length=256, db_column="nam")
|
||||||
email = models.CharField(max_length=256)
|
email = models.CharField(max_length=256, db_column="eml")
|
||||||
sum = models.FloatField()
|
sum = models.DecimalField(max_digits=13, decimal_places=4)
|
||||||
|
|
||||||
view_definition = """
|
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
|
FROM
|
||||||
membershipworks_eventext as eventext,
|
membershipworks_eventext AS eventext,
|
||||||
JSON_TABLE(eventext.details, '$.usr[*]' COLUMNS (
|
jsonb_to_recordset(eventext.details -> 'usr') AS usr (
|
||||||
uid VARCHAR(24) PATH '$.uid',
|
uid TEXT,
|
||||||
name VARCHAR(256) PATH '$.nam',
|
nam TEXT,
|
||||||
email VARCHAR(256) PATH '$.eml',
|
eml TEXT,
|
||||||
sum DOUBLE PATH '$.sum'
|
sum NUMERIC
|
||||||
)) as tkt
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -137,6 +137,7 @@ def scrape_events():
|
|||||||
events = Event.objects.bulk_create(
|
events = Event.objects.bulk_create(
|
||||||
[Event.from_api_dict(event_data) for event_data in data["evt"]],
|
[Event.from_api_dict(event_data) for event_data in data["evt"]],
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
|
unique_fields=["eid"],
|
||||||
update_fields=[
|
update_fields=[
|
||||||
field.attname
|
field.attname
|
||||||
for field in Event._meta.get_fields()
|
for field in Event._meta.get_fields()
|
||||||
|
@ -10,6 +10,7 @@ from django.contrib.auth.mixins import (
|
|||||||
AccessMixin,
|
AccessMixin,
|
||||||
PermissionRequiredMixin,
|
PermissionRequiredMixin,
|
||||||
)
|
)
|
||||||
|
from django.contrib.postgres.aggregates import StringAgg
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.db.models import OuterRef, Q, Subquery
|
from django.db.models import OuterRef, Q, Subquery
|
||||||
@ -33,7 +34,6 @@ import django_tables2 as tables
|
|||||||
import weasyprint
|
import weasyprint
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django_filters.views import BaseFilterView
|
from django_filters.views import BaseFilterView
|
||||||
from django_mysql.models.aggregates import GroupConcat
|
|
||||||
from django_sendfile import sendfile
|
from django_sendfile import sendfile
|
||||||
from django_tables2 import A, SingleTableMixin
|
from django_tables2 import A, SingleTableMixin
|
||||||
from django_tables2.export.views import ExportMixin
|
from django_tables2.export.views import ExportMixin
|
||||||
@ -538,7 +538,7 @@ class MissingPaperworkReport(
|
|||||||
membership=Subquery(
|
membership=Subquery(
|
||||||
qs.filter(
|
qs.filter(
|
||||||
pk=OuterRef("pk"), flags__type__in=("level", "addon")
|
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.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
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.contrib.staticfiles import finders as staticfiles_finders
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
@ -16,14 +17,13 @@ from django.db.models import (
|
|||||||
Value,
|
Value,
|
||||||
When,
|
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.http import HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import weasyprint
|
import weasyprint
|
||||||
from django_mysql.models.aggregates import GroupConcat
|
|
||||||
from django_tables2 import SingleTableMixin
|
from django_tables2 import SingleTableMixin
|
||||||
from django_tables2.export.views import ExportMixin
|
from django_tables2.export.views import ExportMixin
|
||||||
|
|
||||||
@ -158,14 +158,16 @@ class InstructorOrVendorReport(
|
|||||||
.get_table_data()
|
.get_table_data()
|
||||||
.values("name")
|
.values("name")
|
||||||
.annotate(
|
.annotate(
|
||||||
instructor_agreement_date=GroupConcat(
|
instructor_agreement_date=StringAgg(
|
||||||
"instructor_agreement_date", distinct=True, ordering="asc"
|
Cast("instructor_agreement_date", models.TextField()),
|
||||||
|
delimiter=", ",
|
||||||
|
distinct=True,
|
||||||
),
|
),
|
||||||
w9_date=GroupConcat("w9_date", distinct=True, ordering="asc"),
|
w9_date=StringAgg(
|
||||||
phone=GroupConcat("phone", distinct=True, ordering="asc"),
|
Cast("w9_date", models.TextField()), ", ", distinct=True
|
||||||
email_address=GroupConcat(
|
|
||||||
"email_address", distinct=True, ordering="asc"
|
|
||||||
),
|
),
|
||||||
|
phone=StringAgg("phone", ", ", distinct=True),
|
||||||
|
email_address=StringAgg("email_address", ", ", distinct=True),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
89
pdm.lock
89
pdm.lock
@ -5,7 +5,7 @@
|
|||||||
groups = ["default", "debug", "dev", "lint", "server", "typing"]
|
groups = ["default", "debug", "dev", "lint", "server", "typing"]
|
||||||
strategy = ["inherit_metadata"]
|
strategy = ["inherit_metadata"]
|
||||||
lock_version = "4.5.0"
|
lock_version = "4.5.0"
|
||||||
content_hash = "sha256:4a7538bb6a4aabea5a3641a9f69edf6045587a129895ddf5108bf765141fbe6f"
|
content_hash = "sha256:0c560e1f2a81810e95ba8993538337bfc8b39576b6923280cfbfad91ad5b58f2"
|
||||||
|
|
||||||
[[metadata.targets]]
|
[[metadata.targets]]
|
||||||
requires_python = "==3.11.*"
|
requires_python = "==3.11.*"
|
||||||
@ -561,21 +561,6 @@ files = [
|
|||||||
{file = "django_model_utils-4.5.1.tar.gz", hash = "sha256:1220f22d9a467d53a1e0f4cda4857df0b2f757edf9a29955c42461988caa648a"},
|
{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]]
|
[[package]]
|
||||||
name = "django-nh3"
|
name = "django-nh3"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -1439,17 +1424,6 @@ files = [
|
|||||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
{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]]
|
[[package]]
|
||||||
name = "nh3"
|
name = "nh3"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
@ -1624,6 +1598,67 @@ files = [
|
|||||||
{file = "protobuf-5.27.3.tar.gz", hash = "sha256:82460903e640f2b7e34ee81a947fdaad89de796d324bcbc38ff5430bcdead82c"},
|
{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]]
|
[[package]]
|
||||||
name = "ptyprocess"
|
name = "ptyprocess"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -16,7 +16,6 @@ dependencies = [
|
|||||||
"markdownify~=0.13",
|
"markdownify~=0.13",
|
||||||
"mdformat~=0.7",
|
"mdformat~=0.7",
|
||||||
"mdformat-tables~=0.4",
|
"mdformat-tables~=0.4",
|
||||||
"mysqlclient~=2.2",
|
|
||||||
"django-autocomplete-light~=3.11",
|
"django-autocomplete-light~=3.11",
|
||||||
"weasyprint~=62.3",
|
"weasyprint~=62.3",
|
||||||
"requests~=2.32",
|
"requests~=2.32",
|
||||||
@ -34,7 +33,6 @@ dependencies = [
|
|||||||
"tablib[ods,xlsx]~=3.6",
|
"tablib[ods,xlsx]~=3.6",
|
||||||
"django-filter~=24.3",
|
"django-filter~=24.3",
|
||||||
"django-db-views~=0.1",
|
"django-db-views~=0.1",
|
||||||
"django-mysql~=4.14",
|
|
||||||
"django-weasyprint~=2.3",
|
"django-weasyprint~=2.3",
|
||||||
"django-sendfile2~=0.7",
|
"django-sendfile2~=0.7",
|
||||||
"django-bootstrap5~=24.2",
|
"django-bootstrap5~=24.2",
|
||||||
@ -44,6 +42,7 @@ dependencies = [
|
|||||||
"google-api-python-client~=2.142",
|
"google-api-python-client~=2.142",
|
||||||
"google-auth-oauthlib~=1.2",
|
"google-auth-oauthlib~=1.2",
|
||||||
"django-model-utils~=4.5",
|
"django-model-utils~=4.5",
|
||||||
|
"psycopg[binary,pool]~=3.2",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
|||||||
(
|
(
|
||||||
"duration",
|
"duration",
|
||||||
models.GeneratedField(
|
models.GeneratedField(
|
||||||
db_persist=False,
|
db_persist=True,
|
||||||
expression=django.db.models.expressions.CombinedExpression(
|
expression=django.db.models.expressions.CombinedExpression(
|
||||||
models.F("end"), "-", models.F("start")
|
models.F("end"), "-", models.F("start")
|
||||||
),
|
),
|
||||||
|
@ -87,7 +87,7 @@ class Reservation(models.Model):
|
|||||||
duration = models.GeneratedField(
|
duration = models.GeneratedField(
|
||||||
expression=F("end") - F("start"),
|
expression=F("end") - F("start"),
|
||||||
output_field=models.DurationField(),
|
output_field=models.DurationField(),
|
||||||
db_persist=False,
|
db_persist=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = ReservationQuerySet.as_manager()
|
objects = ReservationQuerySet.as_manager()
|
||||||
|
Loading…
Reference in New Issue
Block a user