diff --git a/apps/cfp/views.py b/apps/cfp/views.py index 430fb0d60..7ae0ee865 100644 --- a/apps/cfp/views.py +++ b/apps/cfp/views.py @@ -566,7 +566,15 @@ class FinaliseForm(Form): telephone_number = TelField("Telephone") eventphone_number = TelField("On-site extension", min_length=3, max_length=5) - may_record = BooleanField("I am happy for this to be recorded", default=True) + video_privacy = SelectField( + "Recording", + default="public", + choices=[ + ("public", "I am happy for this to be streamed and recorded"), + ("review", "Do not stream, and do not publish until reviewed"), + ("none", "Do not stream or record"), + ], + ) needs_laptop = BooleanField("I will need to borrow a laptop for slides") equipment_required = TextAreaField("Equipment Required") additional_info = TextAreaField("Additional Information") @@ -684,7 +692,12 @@ class F(FinaliseForm): proposal.telephone_number = form.telephone_number.data proposal.eventphone_number = form.eventphone_number.data - proposal.may_record = form.may_record.data + if form.video_privacy.data == "review" and proposal.video_privacy != "review": + # FIXME move this and the one above to validators + flash("Recording privacy can only be changed to 'review' by an administrator") + return redirect(url_for(".edit_proposal", proposal_id=proposal_id)) + else: + proposal.video_privacy = form.video_privacy.data proposal.needs_laptop = form.needs_laptop.data proposal.equipment_required = form.equipment_required.data proposal.additional_info = form.additional_info.data @@ -740,7 +753,11 @@ class F(FinaliseForm): form.content_note.data = proposal.content_note form.family_friendly.data = proposal.family_friendly - form.may_record.data = proposal.may_record + form.video_privacy.data = proposal.video_privacy + if proposal.video_privacy != "review": + # Don't allow users to choose review themselves + form.video_privacy.choices = [(c, _) for c, _ in form.video_privacy.choices if c != "review"] + form.needs_laptop.data = proposal.needs_laptop form.equipment_required.data = proposal.equipment_required form.additional_info.data = proposal.additional_info diff --git a/apps/cfp_review/base.py b/apps/cfp_review/base.py index a06529361..b34c2f9aa 100644 --- a/apps/cfp_review/base.py +++ b/apps/cfp_review/base.py @@ -507,7 +507,7 @@ def log_and_close(msg, next_page, proposal_id=None): form.departure_period.data = prop.departure_period form.telephone_number.data = prop.telephone_number form.eventphone_number.data = prop.eventphone_number - form.may_record.data = prop.may_record + form.video_privacy.data = prop.video_privacy form.needs_laptop.data = prop.needs_laptop form.available_times.data = prop.available_times form.content_note.data = prop.content_note diff --git a/apps/cfp_review/forms.py b/apps/cfp_review/forms.py index 73c56235f..088636f99 100644 --- a/apps/cfp_review/forms.py +++ b/apps/cfp_review/forms.py @@ -68,7 +68,14 @@ class UpdateProposalForm(Form): departure_period = StringField("Departure time") telephone_number = StringField("Telephone") eventphone_number = StringField("On-site extension") - may_record = BooleanField("May record") + video_privacy = SelectField( + "Recording", + choices=[ + ("public", "Stream and record"), + ("review", "Do not stream, and do not publish until reviewed"), + ("none", "Do not stream or record"), + ], + ) needs_laptop = SelectField( "Needs laptop", choices=[ @@ -141,7 +148,7 @@ def update_proposal(self, proposal): if self.needs_laptop.raw_data: proposal.needs_laptop = self.needs_laptop.data - proposal.may_record = self.may_record.data + proposal.video_privacy = self.video_privacy.data # All these if statements are because this will nuke the data if you # change the state when the fields are currently hidden, so changing diff --git a/apps/schedule/base.py b/apps/schedule/base.py index 8464967be..76ba6b83c 100644 --- a/apps/schedule/base.py +++ b/apps/schedule/base.py @@ -9,7 +9,6 @@ from flask import current_app as app from wtforms import ( - BooleanField, FieldList, FormField, SelectField, @@ -443,7 +442,14 @@ def herald_main(): class HeraldCommsForm(Form): talk_id = HiddenIntegerField() - may_record = BooleanField("Can this be recorded?") + video_privacy = SelectField( + "Recording", + choices=[ + ("public", "Stream and record"), + ("review", "Do not stream, and do not publish until reviewed"), + ("none", "Do not stream or record"), + ], + ) update = SubmitField("Update info") speaker_here = SubmitField("'Now' Speaker here") @@ -493,17 +499,16 @@ def herald_message(message, proposal=None): flash("'now' changed, please refresh") return redirect(url_for(".herald_venue", venue_name=venue_name)) - change = "may" if form.now.may_record else "may not" - msg = herald_message(f"Change: {change} record '{now.title}'", now) - now.may_record = form.now.may_record.data + msg = herald_message(f"Change: video privacy '{now.video_privacy}' for '{now.title}'", now) + now.video_privacy = form.now.video_privacy.data elif form.next.update.data: if next is None or form.next.talk_id.data != next.id: flash("'next' changed, please refresh") return redirect(url_for(".herald_venue", venue_name=venue_name)) - change = "may" if form.next.may_record else "may not" - msg = herald_message(f"Change: {change} record '{next.title}'", next) - next.may_record = form.next.may_record.data + + msg = herald_message(f"Change: video privacy '{now.video_privacy}' for '{next.title}'", next) + next.video_privacy = form.next.video_privacy.data elif form.now.speaker_here.data: msg = herald_message(f"{now.user.name}, arrived.", now) @@ -522,11 +527,11 @@ def herald_message(message, proposal=None): if now: form.now.talk_id.data = now.id - form.now.may_record.data = now.may_record + form.now.video_privacy.data = now.video_privacy if next: form.next.talk_id.data = next.id - form.next.may_record.data = next.may_record + form.next.video_privacy.data = next.video_privacy return render_template( "schedule/herald/venue.html", diff --git a/apps/schedule/data.py b/apps/schedule/data.py index d313b0e38..f128b719e 100644 --- a/apps/schedule/data.py +++ b/apps/schedule/data.py @@ -27,7 +27,7 @@ def _get_proposal_dict(proposal: Proposal, favourites_ids): "user_id": proposal.user.id, "description": proposal.published_description or proposal.description, "type": proposal.type, - "may_record": proposal.may_record, + "video_privacy": proposal.video_privacy, "is_fave": proposal.id in favourites_ids, "is_family_friendly": proposal.family_friendly, "is_from_cfp": not proposal.user_scheduled, diff --git a/apps/schedule/schedule_xml.py b/apps/schedule/schedule_xml.py index 5268c0a63..ec104ee37 100644 --- a/apps/schedule/schedule_xml.py +++ b/apps/schedule/schedule_xml.py @@ -135,7 +135,7 @@ def add_recording(event_node, event): _add_sub_with_text(recording_node, "license", "CC BY-SA 4.0") _add_sub_with_text( - recording_node, "optout", "false" if event.get("may_record") else "true" + recording_node, "optout", "false" if event.get("video_privacy") == "public" else "true" ) if "ccc" in video: _add_sub_with_text(recording_node, "url", video["ccc"]) diff --git a/js/schedule/ScheduleData.jsx b/js/schedule/ScheduleData.jsx index 5ec559495..adc52da42 100644 --- a/js/schedule/ScheduleData.jsx +++ b/js/schedule/ScheduleData.jsx @@ -142,7 +142,7 @@ class ScheduleData { e.endTime = DateTime.fromSQL(e.end_date, { locale: 'en-GB' }); e.officialEvent = e.is_from_cfp; - e.noRecording = !e.may_record && e.officialEvent && (e.type === 'talk' || e.type === 'performance'); + e.noRecording = (e.video_privacy === 'none') && e.officialEvent && (e.type === 'talk' || e.type === 'performance'); if (e.type === 'youthworkshop') { e.humanReadableType = 'Youth Workshop' diff --git a/migrations/versions/e27e0646278c_add_video_privacy.py b/migrations/versions/e27e0646278c_add_video_privacy.py new file mode 100644 index 000000000..bfbe9b6fe --- /dev/null +++ b/migrations/versions/e27e0646278c_add_video_privacy.py @@ -0,0 +1,29 @@ +"""Add video_privacy + +Revision ID: e27e0646278c +Revises: fa176a23edd6 +Create Date: 2024-05-27 18:55:33.131986 + +""" + +# revision identifiers, used by Alembic. +revision = 'e27e0646278c' +down_revision = 'fa176a23edd6' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('proposal', sa.Column('video_privacy', sa.String(), nullable=True)) + op.add_column('proposal_version', sa.Column('video_privacy', sa.String(), autoincrement=False, nullable=True)) + op.execute("""update proposal set video_privacy = (case when may_record then 'public' else 'none' end)""") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('proposal_version', 'video_privacy') + op.drop_column('proposal', 'video_privacy') + # ### end Alembic commands ### diff --git a/models/cfp.py b/models/cfp.py index 301ebf4fe..c53312fe0 100644 --- a/models/cfp.py +++ b/models/cfp.py @@ -401,6 +401,7 @@ class Proposal(BaseModel): telephone_number = db.Column(db.String) eventphone_number = db.Column(db.String) may_record = db.Column(db.Boolean) + video_privacy = db.Column(db.String) needs_laptop = db.Column(db.Boolean) available_times = db.Column(db.String) family_friendly = db.Column(db.Boolean, default=False) @@ -463,6 +464,7 @@ def get_export_data(cls): "one_day", "notice_required", "may_record", + "video_privacy", "state", ] @@ -485,6 +487,7 @@ def get_export_data(cls): "eventphone_number", "telephone_number", "may_record", + "video_privacy", "needs_laptop", "available_times", "attendees", @@ -510,6 +513,7 @@ def get_export_data(cls): cls.departure_period, cls.needs_laptop, cls.may_record, + cls.video_privacy, ).order_by(cls.id) if cls.__name__ == "WorkshopProposal": @@ -558,6 +562,7 @@ def get_export_data(cls): cls.published_names.label("names"), cls.published_pronouns.label("pronouns"), cls.may_record, + cls.video_privacy, cls.scheduled_time, cls.scheduled_duration, Venue.name.label("venue"), diff --git a/templates/cfp/finalise.html b/templates/cfp/finalise.html index 939e6cc6f..16d67964c 100644 --- a/templates/cfp/finalise.html +++ b/templates/cfp/finalise.html @@ -107,7 +107,7 @@
Final Requirements {% if proposal.type in ['talk', 'performance'] %} - {{ render_field(form.may_record, 7) }} + {{ render_field(form.video_privacy, 7) }} {% endif %} {% if proposal.type == 'talk' %} diff --git a/templates/cfp_review/proposal.html b/templates/cfp_review/proposal.html index 844634509..99691a448 100644 --- a/templates/cfp_review/proposal.html +++ b/templates/cfp_review/proposal.html @@ -198,7 +198,7 @@

{{ render_dl_field(form.telephone_number) }} {{ render_dl_field(form.eventphone_number) }} {% if proposal.type in ['talk', 'performance'] %} - {{ render_dl_field(form.may_record) }} + {{ render_dl_field(form.video_privacy) }} {% endif %} {% if proposal.type == 'talk' %} {{ render_radio_field(form.needs_laptop) }} diff --git a/templates/schedule/_proposal_icons.html b/templates/schedule/_proposal_icons.html index 86e4707fd..bc45fbbcb 100644 --- a/templates/schedule/_proposal_icons.html +++ b/templates/schedule/_proposal_icons.html @@ -1,7 +1,7 @@ {# reusable icons #} {% macro proposal_icons(prop) %} {% if prop.type == 'talk' and prop.state == 'finalised' %} - {% if not prop.may_record %} + {% if prop.video_privacy == 'none' %} {% endif %} {% if prop.content_note %} diff --git a/templates/schedule/herald/venue.html b/templates/schedule/herald/venue.html index 2ef78b0ce..03e36000d 100644 --- a/templates/schedule/herald/venue.html +++ b/templates/schedule/herald/venue.html @@ -22,7 +22,7 @@

Now

Duration
{{ now.scheduled_duration }} minutes
{{ form.now.talk_id() }} - {{ render_field(form.now.may_record, horizontal=8) }} + {{ render_field(form.now.video_privacy, horizontal=8) }} {{ render_field(form.now.update, horizontal=8) }}

 

{{ render_field(form.now.speaker_here, horizontal=8) }} @@ -44,7 +44,7 @@

Next

Duration
{{ next.scheduled_duration }} minutes
{{ form.next.talk_id() }} - {{ render_field(form.next.may_record, horizontal=8) }} + {{ render_field(form.next.video_privacy, horizontal=8) }} {{ render_field(form.next.update, horizontal=8) }}

 

{{ render_field(form.next.speaker_here, horizontal=8) }}