Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
6 changes: 6 additions & 0 deletions app/eventyay/base/models/cfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def default_settings():
'count_length_in': 'chars',
'show_deadline': True,
'hide_after_deadline': False,
'cfp_enable_gravatar': True,
}


Expand Down Expand Up @@ -172,3 +173,8 @@ def max_deadline(self) -> dt.datetime:
if self.deadline:
deadlines.append(self.deadline)
return max(deadlines) if deadlines else None

@property
def enable_gravatar(self) -> bool:
"""Check if Gravatar is enabled for this event's CfP."""
return self.settings.get('cfp_enable_gravatar', True)
12 changes: 7 additions & 5 deletions app/eventyay/common/templates/common/avatar.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@
<div class="avatar-upload hide-label">
{{ form.avatar.as_field_group }}
</div>
{{ form.get_gravatar.as_field_group }}
{% if request.event.cfp.enable_gravatar %}
{{ form.get_gravatar.as_field_group }}
{% endif %}
</div>
<div class="form-image-preview {% if not user.avatar and not user.get_gravatar %}d-none{% endif %}">
<a href="{% if user.avatar %}{{ user.avatar|thumbnail:"default" }}{% endif %}"
data-lightbox="{% if user.avatar %}{{ user.avatar_url }}{% endif %}">
<div class="form-image-preview {% if not user.avatar %}{% if not user.get_gravatar or not request.event.cfp.enable_gravatar %}d-none{% endif %}{% endif %}">
<a href="{% if user.avatar %}{{ user.avatar|thumbnail:"default" }}{% endif %}" data-lightbox="{% if user.avatar %}{{ user.avatar_url }}{% endif %}">
<img loading="lazy"
class="avatar"
data-gravatar="{{ user.gravatar_parameter }}"
data-avatar="{% if user.avatar %}{{ user.avatar_url }}{% endif %}"
alt="{% translate "Your avatar" %}"
{% if user.avatar %}src="{{ user.avatar_url }}"{% endif %} />
{% if user.avatar %}src="{{ user.avatar_url }}"{% endif %}
{% if not user.avatar and user.get_gravatar and request.event.cfp.enable_gravatar %}src="https://www.gravatar.com/avatar/{{ user.gravatar_parameter }}?s=140&d=identicon"{% endif %} />
</a>
</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions app/eventyay/orga/forms/cfp.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ class CfPGeneralSettingsForm(ReadOnlyFlag, I18nHelpText, JsonSubfieldMixin, I18n
widget=forms.RadioSelect(),
required=False,
)
cfp_enable_gravatar = forms.BooleanField(
label=_('Enable Gravatar'),
help_text=_('Allow speakers to use Gravatar for their profile picture.'),
required=False,
)

def __init__(self, *args, obj, **kwargs):
kwargs.pop('read_only') # added in ActionFromUrl view mixin, but not needed here.
Expand All @@ -74,6 +79,7 @@ def __init__(self, *args, obj, **kwargs):
if getattr(obj, 'email', None):
self.fields['mail_on_new_submission'].help_text += f' (<a href="mailto:{obj.email}">{obj.email}</a>)'
self.initial['count_length_in'] = obj.cfp.settings.get('count_length_in', 'chars')
self.initial['cfp_enable_gravatar'] = obj.cfp.enable_gravatar

def save(self, *args, **kwargs):
current_count_length_in = self.instance.cfp.settings.get('count_length_in', 'chars')
Expand Down Expand Up @@ -195,6 +201,7 @@ def save(self, *args, **kwargs):
fields_config = self.instance.cfp.settings.get('fields_config')

self.instance.cfp.settings['count_length_in'] = self.cleaned_data.get('count_length_in') or 'chars'
self.instance.cfp.settings['cfp_enable_gravatar'] = self.cleaned_data.get('cfp_enable_gravatar', False)

# Restore fields_config after setting other values (also when it is an empty dict)
if fields_config is not None:
Expand Down
6 changes: 6 additions & 0 deletions app/eventyay/orga/templates/orga/cfp/forms.html
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,12 @@ <h2 class="d-flex">
</td>
<td></td>
<td></td>
<td class="text-center">
<span class="mr-1">{% translate "Enable Gravatar" %}</span>
<label class="toggle-switch" aria-label="{% translate "Enable Gravatar" %}">
{{ sform.cfp_enable_gravatar }}
<span class="toggle-slider"></span>
</label>
<td></td>
<td class="text-center">{{ field_counts.avatar }}</td>
<td class="text-right">
Expand Down
23 changes: 17 additions & 6 deletions app/eventyay/person/forms/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ def __init__(self, *args, name=None, enforce_account_name_match=False, **kwargs)
self.fields.pop('avatar_source', None)
self.fields.pop('avatar_license', None)
self.fields.pop('get_gravatar', None)
else:
if not self.event.cfp.enable_gravatar:
self.fields.pop('get_gravatar', None)
if 'avatar' in self.fields:
self.fields['avatar'].required = False
self.fields['avatar'].widget.is_required = False
if self.is_bound and not self.is_valid() and 'availabilities' in self.errors:
# Replace self.data with a version that uses initial["availabilities"]
# in order to have event and timezone data available
data = self.data.copy()
data['availabilities'] = initial.get('availabilities', [])
self.data = data
elif 'avatar' in self.fields:
self.fields['avatar'].required = False
self.fields['avatar'].widget.is_required = False
Expand Down Expand Up @@ -153,12 +165,11 @@ def clean_email(self):
def clean(self):
data = super().clean()
if self.event.cfp.require_avatar and not data.get('avatar') and not data.get('get_gravatar'):
self.add_error(
'avatar',
forms.ValidationError(
_('Please provide a profile picture or allow us to load your picture from gravatar!')
),
)
if self.event.cfp.enable_gravatar:
msg = _('Please provide a profile picture or allow us to load your picture from gravatar!')
else:
msg = _('Please provide a profile picture!')
self.add_error('avatar', forms.ValidationError(msg))
fullname = self.cleaned_data.get('fullname')
if (
self.enforce_account_name_match
Expand Down
10 changes: 6 additions & 4 deletions app/eventyay/static/orga/js/questionToggles.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ document.addEventListener('DOMContentLoaded', () => {
});

function initToggles() {
// Required status dropdowns - change event updates state
document.querySelectorAll('.required-status-dropdown:not([data-field-id])').forEach(dropdown => {
// Required status dropdowns - change event updates state (only those with data-question-id for AJAX)
document.querySelectorAll('.required-status-dropdown[data-question-id]').forEach(dropdown => {
dropdown.addEventListener('change', handleRequiredDropdownChange);
});

// Binary toggles (active, is_public) - select only those without data-field-id (which are for form page)
document.querySelectorAll('.toggle-switch:not([data-field-id]) input').forEach(input => {
// Binary toggles (active, is_public) - select only those with data-question-id for AJAX
document.querySelectorAll('.toggle-switch[data-question-id] input').forEach(input => {
input.addEventListener('change', handleBinaryToggle);
});
}
Expand Down Expand Up @@ -180,6 +180,8 @@ function initFormPageToggles() {
const requiredDropdown = document.querySelector(`.required-status-dropdown[data-field-id="${escapedId}"]`);
const hiddenInput = document.getElementById(fieldId);

if (!hiddenInput || !requiredDropdown) return;

if (this.checked) {
// Activate - restore previous state or default to 'optional'
let state = hiddenInput.dataset.previousState || requiredDropdown.value;
Expand Down
35 changes: 21 additions & 14 deletions app/tests/talk/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,34 @@
from django_scopes import scope, scopes_disabled
from lxml import etree

from pretalx.common.models.settings import GlobalSettings
from pretalx.event.models import Event, Organiser, Team, TeamInvite
from pretalx.mail.models import MailTemplate
from pretalx.person.models import SpeakerInformation, SpeakerProfile, User, UserApiToken
from pretalx.person.models.auth_token import ENDPOINTS, generate_api_token
from pretalx.schedule.models import Availability, Room, TalkSlot
from pretalx.submission.models import (
from eventyay.base.models.settings import GlobalSettings
from eventyay.base.models.event import Event
from eventyay.base.models.organizer import Organizer as Organiser, Team, TeamInvite
from eventyay.base.models.mail import MailTemplate
from eventyay.base.models.information import SpeakerInformation
from eventyay.base.models.profile import SpeakerProfile
from eventyay.base.models.auth import User
from eventyay.base.models.auth_token import UserApiToken, ENDPOINTS, generate_api_token
from eventyay.base.models.availability import Availability
from eventyay.base.models.room import Room
from eventyay.base.models.slot import TalkSlot
from eventyay.base.models.question import (
Answer,
AnswerOption,
TalkQuestion as Question,
TalkQuestionVariant as QuestionVariant,
TalkQuestionRequired as QuestionRequired,
)
from eventyay.base.models.submission import (
Feedback,
Question,
QuestionVariant,
Resource,
Review,
Submission,
SubmissionType,
SubmitterAccessCode,
Tag,
Track,
)
from pretalx.submission.models.question import QuestionRequired
from eventyay.base.models.resource import Resource
from eventyay.base.models.type import SubmissionType
from eventyay.base.models.access_code import SubmitterAccessCode
from eventyay.base.models.track import Track


@pytest.fixture(scope="session", autouse=True)
Expand Down
107 changes: 107 additions & 0 deletions app/tests/talk/submission/test_cfp_gravatar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import pytest
from eventyay.base.models import CfP
from eventyay.orga.forms.cfp import CfPSettingsForm

@pytest.mark.django_db
def test_cfp_enable_gravatar_default_true(event):
"""Test that enable_gravatar defaults to True when not set"""
# Event fixture creates an event. Check if it has CfP.
if not hasattr(event, 'cfp'):
CfP.objects.create(event=event)

cfp = event.cfp
# Should default to True
assert cfp.enable_gravatar

@pytest.mark.django_db
def test_cfp_enable_gravatar_explicit_false(event):
"""Test that enable_gravatar can be explicitly set to False"""
if not hasattr(event, 'cfp'):
CfP.objects.create(event=event)

cfp = event.cfp
cfp.settings['cfp_enable_gravatar'] = False
cfp.save()

# Should return False when explicitly set
assert not cfp.enable_gravatar

@pytest.mark.django_db
def test_cfp_enable_gravatar_explicit_true(event):
"""Test that enable_gravatar can be explicitly set to True"""
if not hasattr(event, 'cfp'):
CfP.objects.create(event=event)

cfp = event.cfp
cfp.settings['cfp_enable_gravatar'] = True
cfp.save()

# Should return True when explicitly set
assert cfp.enable_gravatar

@pytest.mark.django_db
def test_cfp_enable_gravatar_missing_key(event):
"""Test enable_gravatar behavior when key is missing from settings"""
if not hasattr(event, 'cfp'):
CfP.objects.create(event=event)

cfp = event.cfp

# Ensure key doesn't exist
if 'cfp_enable_gravatar' in cfp.settings:
del cfp.settings['cfp_enable_gravatar']
cfp.save()

# Should still default to True
assert cfp.enable_gravatar

@pytest.mark.django_db
def test_cfp_settings_form_toggle_gravatar(event):
"""Test that CfPSettingsForm correctly updates enable_gravatar setting"""
from django_scopes import scope

if not hasattr(event, 'cfp'):
CfP.objects.create(event=event)

cfp = event.cfp

with scope(event=event):
# Instantiate form to get fields and initial values
form_inst = CfPSettingsForm(obj=event, read_only=False)
form_data = {}

# Populate data for all fields, especially required ones
for name, field in form_inst.fields.items():
# Prefer explicit initial data
if name in form_inst.initial:
form_data[name] = form_inst.initial[name]
# Then field-level initial
elif field.initial is not None:
form_data[name] = field.initial
# Then first choice for ChoiceFields (if required)
elif field.required and hasattr(field, 'choices') and field.choices:
form_data[name] = field.choices[0][0]
# Then boolean defaults
elif isinstance(field, (pytest.importorskip("django.forms").BooleanField)):
form_data[name] = False

# Test setting to False
form_data['cfp_enable_gravatar'] = False

form = CfPSettingsForm(obj=event, read_only=False, data=form_data)

if form.is_valid():
form.save()
assert not cfp.enable_gravatar
else:
pytest.fail(f"Form invalid: {form.errors}")

# Test setting back to True
form_data['cfp_enable_gravatar'] = True
form = CfPSettingsForm(obj=event, read_only=False, data=form_data)

if form.is_valid():
form.save()
assert cfp.enable_gravatar
else:
pytest.fail(f"Form invalid: {form.errors}")
Loading