Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/base/dev/content_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"talk": {
"length": {"default": "30min", "display_name": "talk duration"}
},
"workshop": {
"capacity": {"default": "15", "display_name": "workshop capacity"},
"length": {"default": "1hour", "display_name": "workshop duration"},
"ticketed": {"default": "False"}
},
"installation": {
"size": {"default": "phone booth"}
}
}
39 changes: 37 additions & 2 deletions apps/base/dev/tasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
""" Development CLI tasks """
"""Development CLI tasks"""

import json
import click

from pendulum import Duration as Offset, parse
from flask import current_app as app
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
Expand All @@ -14,10 +17,16 @@
from models.payment import BankAccount
from models.site_state import SiteState, refresh_states
from models.feature_flag import FeatureFlag, refresh_flags
from models.content.content_type_definition import (
ContentTypeDefinition,
ContentTypeFieldDefinition,
)

from . import dev_cli
from .fake import FakeDataGenerator

CONTENT_TYPE_DEFINITIONS_FILE = "apps/base/dev/content_types.json"


@dev_cli.command("data")
@click.pass_context
Expand Down Expand Up @@ -56,7 +65,6 @@ def enable_cfp():
refresh_states()



@dev_cli.command("volunteer_data")
def volunteer_data():
"""Make fake volunteer system data"""
Expand Down Expand Up @@ -632,3 +640,30 @@ def create_bank_accounts():
pass

db.session.commit()


@dev_cli.command("create_content_types")
def create_content_types():
app.logger.info(f"Adding content type definitions")
with open(CONTENT_TYPE_DEFINITIONS_FILE, "r") as file:
data = json.load(file)

# FIXME make this update correctly
for content_type_name, type_field_defs in data.items():
app.logger.info(f"Adding {content_type_name} definition")
content_type = ContentTypeDefinition(content_type_name)
db.session.add(content_type)
db.session.commit()

for field_name, field_def in type_field_defs.items():
db.session.add(
ContentTypeFieldDefinition(
content_type,
field_name,
field_def.get("display_name", None),
field_def.get("default", None),
)
)
db.session.commit()
db.session.flush()
app.logger.info("Done")
44 changes: 44 additions & 0 deletions migrations/versions/8152e3d020c3_add_new_content_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Add new content tables

Revision ID: 8152e3d020c3
Revises: 7249f66e2ae0
Create Date: 2025-07-11 16:59:00.698575

"""

# revision identifiers, used by Alembic.
revision = '8152e3d020c3'
down_revision = '7249f66e2ae0'

from alembic import op
import sqlalchemy as sa


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('content_type_definition',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id', name=op.f('pk_content_type_definition'))
)
op.create_index(op.f('ix_content_type_definition_name'), 'content_type_definition', ['name'], unique=True)
op.create_table('content_type_field_definition',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('display_name', sa.String(), nullable=False),
sa.Column('default', sa.String(), nullable=True),
sa.Column('type_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['type_id'], ['content_type_definition.id'], name=op.f('fk_content_type_field_definition_type_id_content_type_definition')),
sa.PrimaryKeyConstraint('id', name=op.f('pk_content_type_field_definition'))
)
op.create_index(op.f('ix_content_type_field_definition_type_id'), 'content_type_field_definition', ['type_id'], unique=False)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_content_type_field_definition_type_id'), table_name='content_type_field_definition')
op.drop_table('content_type_field_definition')
op.drop_index(op.f('ix_content_type_definition_name'), table_name='content_type_definition')
op.drop_table('content_type_definition')
# ### end Alembic commands ###
63 changes: 63 additions & 0 deletions models/content/content_type_definition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
ContentTypeDefinition - we need a way to track which content types we have
defined and which attributes they expect to exist

ContentTypeFieldDefinition - defines a field used by a content type.

We assume that all proposal types will have a corresponding schedule item type.
"""

from main import db
from .. import BaseModel


class ContentTypeDefinition(BaseModel):
__tablename__ = "content_type_definition"

id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, index=True)

fields = db.relationship("ContentTypeFieldDefinition", backref="type")

def __init__(self, name: str):
self.name = name


class ContentTypeFieldDefinition(BaseModel):
__tablename__ = "content_type_field_definition"

id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
display_name = db.Column(db.String, nullable=False)

# if default is Null/None it is a required field
default = db.Column(db.String, nullable=True)

type_id = db.Column(
db.Integer,
db.ForeignKey("content_type_definition.id"),
nullable=False,
index=True,
)

def __init__(
self,
content_type: ContentTypeDefinition,
name: str,
display_name: str | None = None,
default: str | None = None,
):
self.type_id = content_type.id
self.name = name.lower()

if display_name is None:
self.display_name = name.lower()
else:
self.display_name = display_name

if default is not None:
self.default = default

@property
def is_required(self):
return self.default is None
41 changes: 41 additions & 0 deletions models/content/occurrence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Occurrence – an instance of a ScheduleItem happening at a specific time in a
Venue (we provide for, but don’t currently actively support, multiple
Occurrences of a single ScheduleItem)
"""

from datetime import datetime

from main import db
from .. import BaseModel


class Occurrence(BaseModel):
__versioned__ = {}
__tablename__ = "occurrence"

id = db.Column(db.Integer, primary_key=True)
schedule_item_id = db.Column(
db.Integer, db.ForeignKey("schedule_item.id", nullable=False)
)

time_block_id = db.Column(
db.Integer, db.ForeignKey("time_block.id"), nullable=False
)

created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
modified = db.Column(
db.DateTime, default=datetime.utcnow, nullable=False, onupdate=datetime.utcnow
)

potential_start_time = db.Column(db.String)
potential_time_block_id = db.Column(db.String)
scheduled_start_time = db.Column(db.String)
scheduled_time_block_id = db.Column(db.String)

index = db.Column(db.Integer)

c3voc_url = db.Column(db.String)
youtube_url = db.Column(db.String)
thumbnail_url = db.Column(db.String)
video_recording_lost = db.Column(db.Boolean, nullable=False, default=False)
67 changes: 67 additions & 0 deletions models/content/proposal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Proposal – a proposed piece of content to be reviewed by the EMF team (this is
what the CfP form generates). Proposals now only track the review state - when
a proposal for a talk or workshop is accepted, a corresponding ScheduleItem
is created.
"""

from datetime import datetime

from main import db
from ..cfp_tag import (
ProposalTag,
) # fixme copy into directory & make filename/classname match
from .. import BaseModel


UserProposal = db.Table(
"user_proposal",
BaseModel.metadata,
db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True),
db.Column(
"proposal_id",
db.Integer,
db.ForeignKey("proposal.id"),
primary_key=True,
index=True,
),
)


class Proposal(BaseModel):
# FIXME remove _v2
__tablename__ = "proposal_v2"

id = db.Column(db.Integer, primary_key=True)
users = db.relationship(
"User", secondary=UserProposal, backref=db.backref("proposals")
)

type = db.Column(db.string, nullable=False)
state = db.Column(db.string, nullable=False, default="new")

title = db.Column(db.string, nullable=False)
description = db.Column(db.string, nullable=False)

needs_money = db.Column(db.Boolean, nullable=False, default=False)
needs_help = db.Column(db.Boolean, nullable=False, default=False)
private_notes = db.Column(db.string)

tags = db.relationship(
"Tag",
backref="proposals",
cascade="all",
secondary=ProposalTag,
)

created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
modified = db.Column(
db.DateTime, default=datetime.utcnow, nullable=False, onupdate=datetime.utcnow
)

has_rejected_email = db.Column(db.Boolean, nullable=False, default=False)

schedule_item = db.relationship("ScheduleItem", backref="proposal")

messages = db.relationship("CFPMessage", backref="proposal")
votes = db.relationship("CFPVotes", backref="proposal")
27 changes: 27 additions & 0 deletions models/content/proposal_attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
ProposalAttribute – key/value store of additional information associated with
a Proposal (e.g. workshop capacity, equipment required)
"""

from datetime import datetime

from main import db
from .. import BaseModel


class ProposalAttribute(BaseModel):
__versioned__ = {}
__tablename__ = "proposal_attribute"

id = db.Column(db.Integer, primary_key=True)
proposal_item_id = db.Column(
db.Integer, db.ForeignKey("proposal.id", nullable=False)
)

created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
modified = db.Column(
db.DateTime, default=datetime.utcnow, nullable=False, onupdate=datetime.utcnow
)

key = db.Column(db.string, nullable=False, index=True)
value = db.Column(db.string)
27 changes: 27 additions & 0 deletions models/content/schedule_attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
ScheduleAttribute – key/value store of additional information associated with
a ScheduleItem (e.g. workshop capacity, equipment required)
"""

from datetime import datetime

from main import db
from .. import BaseModel


class ScheduleAttribute(BaseModel):
__versioned__ = {}
__tablename__ = "schedule_attribute"

id = db.Column(db.Integer, primary_key=True)
schedule_item_id = db.Column(
db.Integer, db.ForeignKey("schedule_item.id", nullable=False)
)

created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
modified = db.Column(
db.DateTime, default=datetime.utcnow, nullable=False, onupdate=datetime.utcnow
)

key = db.Column(db.string, nullable=False, index=True)
value = db.Column(db.string)
Loading
Loading