Skip to content

Commit

Permalink
Merge pull request #187 from odkhang/sync-upstream-with-conflict
Browse files Browse the repository at this point in the history
Sync upstream with conflict
  • Loading branch information
mariobehling authored Aug 28, 2024
2 parents 3beda5e + f2870a8 commit d2a617d
Show file tree
Hide file tree
Showing 166 changed files with 52,412 additions and 44,480 deletions.
17 changes: 15 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,21 @@ jobs:
- name: Spellcheck docs
run: make spelling
working-directory: ./doc
- name:
run: '[ ! -s _build/spelling/output.txt ]'
- name: Put spelling errors into summary file
run: |
if [ -z "$(find _build -type f -name '*.spelling')" ]; then
echo "No spelling errors found."
exit 0
fi
echo "## Spellcheck results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
for file in $(find _build -type f -name "*.spelling"); do
sed 's/^/- /' < $file >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
working-directory: ./doc
- name: Fail if there were spelling errors
run: '! find _build -type f -name "*.spelling" | grep -q .'
working-directory: ./doc

linkcheck:
Expand Down
1 change: 1 addition & 0 deletions doc/api/resources/submissions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Endpoints
:query submission_type: Filter submissions by submission type
:query state: Filter submission by state. Will filter by multiple states if you provide multiple state arguments.
:query questions: Pass a comma separated list of question IDs to load, or the string "all" to return all answers.
:query is_featured: Filter by the ``is_featured`` field (``true`` or ``false``).

.. http:get:: /api/events/(event)/submissions/{code}
Expand Down
1 change: 1 addition & 0 deletions doc/api/resources/talks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Endpoints
:query submission_type: Filter submissions by submission type
:query state: Filter submission by state
:query questions: Pass a comma separated list of question IDs to load, or the string 'all' to return all answers.
:query is_featured: Filter by the ``is_featured`` field (``true`` or ``false``).

.. http:get:: /api/events/(event)/talks/{code}
Expand Down
20 changes: 19 additions & 1 deletion doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
.. _changelog:

Release Notes
- :feature:`orga:sidebar` Renamed CfP to Call for Speakers for clarity
=============

- :feature:`schedule,1794` The iCal schedule export has been made private (available only to organisers) as the utility of importing a conference's entire schedule is limited, and people were frustrated that the iCal export did not reflect any applied schedule filters.
- :bug:`schedule,1803` The QR code for schedule exporter links was not showing up when hovering on the QR code symbol.
- :release:`2024.2.1 <2024-08-07>`
- :bug:`orga` The plugin list was always shown as empty, even when there were plugins installed.
- :feature:`orga` Administrators can now also deactivate user accounts, which will log out that user and won't allow them to log in again.
- :release:`2024.2.0 <2024-08-06>`
- :bug:`orga` The markdown preview posed a security vulnerability by allowing speakers and organisers to include unsafe JavaScript. This JavaScript would only be executed when accessing the preview, i.e. when a speaker or organiser opened to proposal page (not attendees or the public). Thanks to Jorian Woltjer for reporting this issue.
- :feature:`api` The submission API now has a filter for the ``is_featured`` field.
- :feature:`cfp,1761` In the CfP submission multi-step form, the tab title now reflects the proposal title, to make it easier to work on multiple proposal submissions at the same time.
- :bug:`orga:speaker,1768` When filtering the speaker list by only accepted/confirmed speakers, the listed proposal count would be incorrect (inflated).
- :feature:`cfp,1574` pretalx now supports the ``~~`` strikethrough syntax in Markdown.
- :bug:`orga:schedule,1702` Sessions starting at exactly midnight of the first day of the event would not show up in the schedule editor (but could be scheduled there by dropping them on the day heading).
- :feature:`orga:schedule,1730` The schedule editor now allows you to schedule talks that are only "pending accepted" (i.e. the speaker has not yet received the acceptance email), so that organisers can try out how their schedule would look with a given number of tentatively accepted proposals.
- :feature:`orga` Administrators (i.e. instance owners) can now search a list of all users, which includes their teams and permissions, and links to trigger account deletion and password resets.
- :bug:`orga:review` Assigning reviewers could lead to incorrect assignments when browsers cached the form, but new reviewers were added to the team, shifting the overall order of input fields.
- :feature:`cfp` Choice and multiple choice questions now use a drop-down with typeahead (search for options) when they have a lot of options.
- :feature:`orga,1079` All images in forms in the organiser area now include a preview of the saved image, and open a lightbox instead of the image file when clicked.
- :announcement:`admin` We now recommend that you use a virtualenv instead of the ``pip --user`` installation method, and have updated our install and upgrade documentation accordingly.
- :bug:`orga` While organisers could reorder questions, and the order was saved and used in the frontend, the new order was not shown in the organiser backend.
- :feature:`orga` All tables in the organiser area now come with sticky headers, to accommodate the possible increased length of the tables.
Expand Down
6 changes: 3 additions & 3 deletions doc/maintainer/release.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ System checks
-------------

1. Deploy the release-ready commit to an instance. Check if the upgrade and the instance works.
2. Clone pretalx into a clean repo: ``git clone [email protected]:pretalx/pretalx pretalx-release && cd pretalx-release``
3. Set up your environment: ``mkvirtualenv pretalx-release && pip install -e . check-manifest twine``
2. Clean clone: ``git clone [email protected]:pretalx/pretalx pretalx-release && cd pretalx-release``
3. Set up your environment: ``mkvirtualenv pretalx-release && pip install -e . check-manifest twine wheel``
4. Run ``check-manifest`` **locally**.

Take-off and landing
Expand All @@ -37,7 +37,7 @@ Take-off and landing
3. Make a release commit: ``RELEASE=vx.y.z; git commit -am "Release $RELEASE" && git tag -m "Release $RELEASE" $RELEASE``
4. Build a new release: ``rm -rf dist/ build/ pretalx.egg-info && python -m build -n``
5. Upload the release: ``twine upload dist/pretalx-*``
6. Push the release: ``git push && git push --tags``
6. Push the release: ``git push``
7. Install/update the package somewhere.
8. Publish the blog post.
9. Add the release on `GitHub <https://github.com/pretalx/pretalx/releases>`_ (upload the archive you uploaded to PyPI, and add a link to the correct section of the :ref:`changelog`)
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ dependencies = [
"csscompressor~=0.9.0",
"cssutils~=2.9.0",
"defusedcsv~=2.0.0",
"Django~=4.2.0",
"defusedxml~=0.7.0",
"Django[argon2]~=4.2.0",
"django-bootstrap4~=3.0.0",
"django-compressor~=4.4.0",
"django-context-decorator",
Expand Down
2 changes: 1 addition & 1 deletion src/pretalx/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2024.2.0.dev0"
__version__ = "2024.3.0.dev0"
3 changes: 1 addition & 2 deletions src/pretalx/agenda/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ def is_html_export(request):
the form HTTP_ORIGINAL_NAME, so that 'is_html_export' cannot be
faked from the outside.
"""
context = {"is_html_export": request.META.get("is_html_export") is True}
return context
return {"is_html_export": request.META.get("is_html_export") is True}
49 changes: 24 additions & 25 deletions src/pretalx/agenda/management/commands/export_schedule_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ def get(url):
except FileNotFoundError:
# … then fall back to asking the views.
response = client.get(url, is_html_export=True, HTTP_ACCEPT="text/html")
content = get_content(response)
return content
return get_content(response)

yield get

Expand All @@ -45,7 +44,7 @@ def find_assets(html):
for asset in soup.find_all(["script", "img"]):
yield asset.attrs["src"]
for asset in soup.find_all(["link"]):
if asset.attrs["rel"][0] in ["icon", "stylesheet"]:
if asset.attrs["rel"][0] in ("icon", "stylesheet"):
yield asset.attrs["href"]


Expand Down Expand Up @@ -116,12 +115,12 @@ def dump_content(destination, path, getter):
path += "index.html"

path = (Path(destination) / path.lstrip("/")).resolve()
if not Path(destination) in path.parents:
if Path(destination) not in path.parents:
raise CommandError("Path traversal detected, aborting.")
path.parent.mkdir(parents=True, exist_ok=True)

with open(path, "wb") as f:
f.write(content)
with open(path, "wb") as output_file:
output_file.write(content)
return content


Expand All @@ -141,37 +140,37 @@ def get_mediastatic_content(url):
):
raise FileNotFoundError()

with open(local_path, "rb") as f:
return f.read()
with open(local_path, "rb") as media_file:
return media_file.read()


def export_event(event, destination):
with (
override_settings(COMPRESS_ENABLED=True, COMPRESS_OFFLINE=True),
override_timezone(event.timezone),
fake_admin(event) as get,
):
with fake_admin(event) as get:
logging.info("Collecting URLs for export")
urls = [*event_urls(event)]
assets = set()
logging.info("Collecting URLs for export")
urls = list(event_urls(event))
assets = set()

logging.info(f"Exporting {len(urls)} pages")
for url in map(get_path, urls):
content = dump_content(destination, url, get)
assets |= set(map(get_path, find_assets(content)))
logging.info(f"Exporting {len(urls)} pages")
for url in map(get_path, urls):
content = dump_content(destination, url, get)
assets |= set(map(get_path, find_assets(content)))

css_assets = set()
css_assets = set()

logging.info(f"Exporting {len(assets)} static files from HTML links")
for url in assets:
content = dump_content(destination, url, get)
logging.info(f"Exporting {len(assets)} static files from HTML links")
for url in assets:
content = dump_content(destination, url, get)

if url.endswith(".css"):
css_assets |= set(find_urls(content))
if url.endswith(".css"):
css_assets |= set(find_urls(content))

logging.info(f"Exporting {len(css_assets)} files from CSS links")
for url_path in (get_path(urllib.parse.unquote(url)) for url in css_assets):
dump_content(destination, url_path, get)
logging.info(f"Exporting {len(css_assets)} files from CSS links")
for url_path in (get_path(urllib.parse.unquote(url)) for url in css_assets):
dump_content(destination, url_path, get)


def delete_directory(path):
Expand Down
14 changes: 6 additions & 8 deletions src/pretalx/agenda/phrases.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy as _

from pretalx.common.phrases import BasePhrases, Phrases
from pretalx.common.text.phrases import Phrases


class AgendaPhrases(Phrases, app="agenda"):
feedback_success = [
_("Thank you for your feedback!"),
_("Thanks, we (and our speakers) appreciate your feedback!"),
]

feedback_send = BasePhrases.send + [
_("Send feedback"),
_("Send review"),
]

schedule_do_not_record = _("This session will not be recorded.")

view_schedule = _("View conference schedule")
view_schedule_preview = _("View schedule preview")
view_own_submissions = _("Edit or view your proposals")
1 change: 1 addition & 0 deletions src/pretalx/agenda/templates/agenda/feedback_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

{% block content %}
<script src="{% static "vendored/marked.min.js" %}" defer></script> {# do not compress #}
<script src="{% static "vendored/purify.min.js" %}" defer></script>
<script src="{% static "common/js/formTools.js" %}" defer></script>

<h3 class="talk-title">
Expand Down
2 changes: 1 addition & 1 deletion src/pretalx/agenda/templates/agenda/header_row.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</a>
{% if request.event.display_settings.ticket_link %}
<a href="{{ request.event.display_settings.ticket_link }}" class="btn btn-outline-success">
<i class="fa fa-group"></i> {% translate "Tickets" %}
<i class="fa fa-ticket"></i> {% translate "Tickets" %}
</a>
{% endif %}
</div>
Expand Down
24 changes: 13 additions & 11 deletions src/pretalx/agenda/views/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
class ScheduleMixin:
@cached_property
def version(self):
if "version" in self.kwargs:
return unquote(self.kwargs["version"])
if version := self.kwargs.get("version"):
return unquote(version)
return None

def get_object(self):
Expand All @@ -50,8 +50,8 @@ def schedule(self):
return self.get_object()

def dispatch(self, request, *args, **kwargs):
if "version" in request.GET:
kwargs["version"] = request.GET["version"]
if version := request.GET.get("version"):
kwargs["version"] = version
return HttpResponsePermanentRedirect(
reverse(
f"agenda:versioned-{request.resolver_match.url_name}",
Expand Down Expand Up @@ -135,7 +135,7 @@ def get(self, request, *args, **kwargs):
if request.headers.get("If-None-Match") == etag:
return HttpResponseNotModified()
headers = {"ETag": etag}
if file_type not in ["application/json", "text/xml"]:
if file_type not in ("application/json", "text/xml"):
headers["Content-Disposition"] = (
f'attachment; filename="{safe_filename(file_name)}"'
)
Expand Down Expand Up @@ -170,7 +170,7 @@ def get_text(self, request, **kwargs):
"""
)
output_format = request.GET.get("format", "table")
if output_format not in ["list", "table"]:
if output_format not in ("list", "table"):
output_format = "table"
try:
result = draw_ascii_schedule(data, output_format=output_format)
Expand Down Expand Up @@ -222,10 +222,10 @@ def get_object(self):

@context
def exporters(self):
return list(
return [
exporter(self.request.event)
for _, exporter in register_data_exporters.send(self.request.event)
)
]

@context
def my_exporters(self):
Expand All @@ -242,6 +242,10 @@ def show_talk_list(self):
)


def talk_sort_key(talk):
return (talk.start, talk.submission.title if talk.submission else "")


class ScheduleNoJsView(ScheduleView):
template_name = "agenda/schedule_nojs.html"

Expand All @@ -255,9 +259,7 @@ def get_schedule_data(self):
for date in data:
rooms = date.pop("rooms")
talks = [talk for room in rooms for talk in room.get("talks", [])]
talks.sort(
key=lambda x: (x.start, x.submission.title if x.submission else "")
)
talks.sort(key=talk_sort_key)
date["talks"] = talks
return {"data": list(data)}

Expand Down
2 changes: 1 addition & 1 deletion src/pretalx/agenda/views/talk.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from pretalx.agenda.signals import register_recording_provider
from pretalx.cfp.views.event import EventPageMixin
from pretalx.common.mixins.views import PermissionRequired, SocialMediaCardMixin
from pretalx.common.phrases import phrases
from pretalx.common.text.phrases import phrases
from pretalx.schedule.models import Schedule, TalkSlot
from pretalx.submission.forms import FeedbackForm
from pretalx.submission.models import QuestionTarget, Submission, SubmissionStates
Expand Down
4 changes: 2 additions & 2 deletions src/pretalx/agenda/views/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ def widget_script(request, event):
widget_file = "agenda/js/pretalx-schedule.js"
else:
widget_file = "agenda/js/pretalx-schedule.min.js"
f = finders.find(widget_file)
with open(f, encoding="utf-8") as fp:
file_path = finders.find(widget_file)
with open(file_path, encoding="utf-8") as fp:
code = fp.read()
data = code.encode()
return HttpResponse(data, content_type="text/javascript")
35 changes: 18 additions & 17 deletions src/pretalx/api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@


class ApiPermission(BasePermission):

def get_permission_object(self, view, obj, request, detail=False):
if func := getattr(view, "get_permission_object", None):
return func()
return obj or self.request.event

def has_permission(self, request, view):
return self._has_permission(view, getattr(request, "event", None), request)

def has_object_permission(self, request, view, obj):
return self._has_permission(view, obj, request)

def _has_permission(self, view, obj, request):
event = getattr(request, "event", None)
if not event: # Only true for root API view
Expand All @@ -20,24 +32,19 @@ def _has_permission(self, view, obj, request):
return request.user.has_perm(write_permission, permission_object)
return False

def get_permission_object(self, view, obj, request, detail=False):
if hasattr(view, "get_permission_object"):
return view.get_permission_object()
return obj or self.request.event

def has_permission(self, request, view):
return self._has_permission(view, getattr(request, "event", None), request)

def has_object_permission(self, request, view, obj):
return self._has_permission(view, obj, request)


class PluginPermission(ApiPermission):
"""Use this class to restrict access to views based on active plugins.
Set PluginPermission.plugin_required to the name of the plugin that is required to access the endpoint.
"""

def has_permission(self, request, view):
return self._has_permission(view, request)

def has_object_permission(self, request, view, obj):
return self._has_permission(view, request)

def _has_permission(self, view, request):
event = getattr(request, "event", None)
if not event:
Expand All @@ -47,9 +54,3 @@ def _has_permission(self, view, request):
if not plugin_name:
return True
return plugin_name in event.plugin_list

def has_permission(self, request, view):
return self._has_permission(view, request)

def has_object_permission(self, request, view, obj):
return self._has_permission(view, request)
Loading

0 comments on commit d2a617d

Please sign in to comment.