Skip to content

Commit

Permalink
Integrated the django-bittersweet code we use
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuastegmaier committed Apr 17, 2024
1 parent e0b4208 commit d6fbfaf
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 9 deletions.
13 changes: 9 additions & 4 deletions concordia/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import time
from http import HTTPStatus

from bittersweet.models import validated_get_or_create
from celery import Celery
from django.apps import apps
from django.contrib import messages
Expand All @@ -17,10 +16,14 @@
from django.utils.text import slugify
from django.views import View
from django.views.decorators.cache import never_cache
from tabular_export.core import export_to_csv_response as _export_to_csv_response
from tabular_export.core import flatten_queryset

from concordia.models import Asset, Item, Transcription, TranscriptionStatus
from concordia.models import (
Asset,
Item,
Transcription,
TranscriptionStatus,
validated_get_or_create,
)
from exporter.views import do_bagit_export
from importer.models import ImportItem, ImportItemAsset, ImportJob
from importer.tasks import (
Expand All @@ -29,6 +32,8 @@
redownload_image_task,
)
from importer.utils.excel import slurp_excel
from tabular_export.core import export_to_csv_response as _export_to_csv_response
from tabular_export.core import flatten_queryset

from ..models import Campaign, Project, SiteReport
from .forms import AdminProjectBulkImportForm, AdminRedownloadImagesForm
Expand Down
33 changes: 33 additions & 0 deletions concordia/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import os.path
import time
from itertools import chain
from logging import getLogger

import pytesseract
Expand Down Expand Up @@ -988,3 +989,35 @@ class Guide(models.Model):

def __str__(self):
return self.title


def validated_get_or_create(klass, **kwargs):
"""
Similar to :meth:`~django.db.models.query.QuerySet.get_or_create` but uses
the methodical get/save including a full_clean() call to avoid problems with
models which have validation requirements which are not completely enforced
by the underlying database.
For example, with a django-model-translation we always want to go through
the setattr route rather than inserting into the database so translated
fields will be mapped according to the active language. This avoids normally
impossible situations such as creating a record where `title` is defined but
`title_en` is not.
Originally from https://github.com/acdha/django-bittersweet
"""

defaults = kwargs.pop("defaults", {})

try:
obj = klass.objects.get(**kwargs)
return obj, False
except klass.DoesNotExist:
obj = klass()

for k, v in chain(kwargs.items(), defaults.items()):
setattr(obj, k, v)

obj.full_clean()
obj.save()
return obj, True
1 change: 0 additions & 1 deletion concordia/settings_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
# Replaces "django.contrib.staticfiles",
"concordia.apps.ConcordiaStaticFilesConfig",
"bootstrap4",
"bittersweet.apps.BittersweetAppConfig",
"maintenance_mode",
"concordia.apps.ConcordiaAppConfig",
"exporter",
Expand Down
1 change: 0 additions & 1 deletion concordia/templates/account/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
{% load humanize %}
{% load staticfiles %}
{% load bootstrap4 %}
{% load bittersweet_querystring %}

{% block prefetch %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@duetds/[email protected]/dist/duet/themes/default.css" />
Expand Down
2 changes: 1 addition & 1 deletion concordia/templates/fragments/activity-filter-sort.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load bittersweet_querystring %}
{% load concordia_querystring %}
<div class="col-sm">
<ul>
<li><a href="/transcribe">Transcribe</a></li>
Expand Down
2 changes: 1 addition & 1 deletion concordia/templates/fragments/recent-pages.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load bittersweet_querystring %}
{% load concordia_querystring %}

<h2>Recent Pages Worked On</h2>
<div>View all the pages you contributed to in the last 6 months.</div>
Expand Down
2 changes: 1 addition & 1 deletion concordia/templates/fragments/standard-pagination.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load bittersweet_querystring %}
{% load concordia_querystring %}

{% comment %}
This template fragment assumes that you are using Bootstrap's default pagination
Expand Down
130 changes: 130 additions & 0 deletions concordia/templatetags/concordia_querystring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# encoding: utf-8
"""
Query String manipulation filters
Originally from https://github.com/acdha/django-bittersweet
"""

from django.http import QueryDict
from django.template import Library, Node, TemplateSyntaxError, Variable
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _

register = Library()


class QueryStringAlterer(Node):
"""
Query String alteration template tag
Receives a query string (either text or a QueryDict such as request.GET)
and a list of changes to apply. The result will be returned as text query
string, allowing use like this::
<a href="?{% qs_alter request.GET type=object.type %}">{{ label }}</a>
There are two available alterations:
Assignment:
name=var
Deletion - removes the named parameter:
delete:name
Delete a parameter matching a value:
delete_value:"name",value
Delete a parameter matching a value from another variable:
delete_value:field_name,value
Examples:
Query string provided as QueryDict::
{% qs_alter request.GET foo=bar %}
{% qs_alter request.GET foo=bar baaz=quux %}
{% qs_alter request.GET foo=bar baaz=quux delete:corge %}
Remove one facet from a list::
{% qs_alter request.GET foo=bar baaz=quux delete_value:"facets",value %}
Query string provided as string::
{% qs_alter "foo=baaz" foo=bar %}
Any query string may be stored in a variable in the local template context by
making the last argument "as variable_name"::
{% qs_alter request.GET foo=bar baaz=quux delete:corge as new_qs %}
"""

def __init__(self, base_qs, as_variable, *args):
self.base_qs = Variable(base_qs)
self.args = args
# Control whether we return the result or save it to the context:
self.as_variable = as_variable

def render(self, context):
base_qs = self.base_qs.resolve(context)

if isinstance(base_qs, QueryDict):
qs = base_qs.copy()
else:
qs = QueryDict(base_qs, mutable=True)

for arg in self.args:
if arg.startswith("delete:"):
v = arg[7:]
if v in qs:
del qs[v]
elif arg.startswith("delete_value:"):
field, value = arg[13:].split(",", 2)
value = Variable(value).resolve(context)

if not (field[0] == '"' and field[-1] == '"'):
field = Variable(field).resolve(context)
else:
field = field.strip("\"'")

f_list = qs.getlist(field)
if value in f_list:
f_list.remove(value)
qs.setlist(field, f_list)

else:
k, v = arg.split("=", 2)
qs[k] = Variable(v).resolve(context)

encoded_qs = escape(qs.urlencode())
if self.as_variable:
context[self.as_variable] = encoded_qs
return ""
else:
return encoded_qs

@classmethod
def qs_alter_tag(cls, parser, token):
try:
bits = token.split_contents()
except ValueError as err:
raise TemplateSyntaxError(
_(
"qs_alter requires at least two arguments: the initial query string"
" and at least one alteration"
)
) from err

if bits[-2] == "as":
as_variable = bits[-1]
bits = bits[0:-2]
else:
as_variable = None

return QueryStringAlterer(bits[1], as_variable, *bits[2:])


register.tag("qs_alter", QueryStringAlterer.qs_alter_tag)

0 comments on commit d6fbfaf

Please sign in to comment.