18 | {% endif %}
19 |
--------------------------------------------------------------------------------
/src/spectator/core/templates/spectator_core/includes/card_chart.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | For displaying a list of objects in a chart within a card.
3 |
4 | Expects:
5 |
6 | * card_title - The text for the card's heading.
7 |
8 | * object_list - A list/QuerySet of objects.
9 |
10 | * score_attr - The name of the attribute on each object that contains the score
11 | for the chart. e.g. 'num_visits'
12 |
13 | * name_attr - Optional. The name of the attribute on each object to be used for
14 | its display value. Default is 'name'.
15 |
16 | * in_card - Optional. Boolean, default is False. Is this displayed within a
17 | card element?
18 |
19 | * use_cite - If True, then wrap the name of each object in tags. Default: False.
20 |
21 | {% endcomment %}
22 |
23 | {% if object_list %}
24 |
25 |
26 |
{{ card_title }}
27 |
28 | {% include 'spectator_core/includes/chart.html' with object_list=object_list score_attr=score_attr name_attr=name_attr|default:None in_card=True use_cite=use_cite|default_if_none:False only %}
29 |
16 |
--------------------------------------------------------------------------------
/src/spectator/core/templates/spectator_core/includes/chart.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | For displaying a list of objects in a chart.
3 |
4 | Expects:
5 | * object_list - A list/QuerySet of objects, each having a `chart_position` attribue.
6 | OR, each item could be a list of objects, again with `chart_position` attributes (presumably each would have the same `chart_position` value).
7 |
8 | * score_attr - The name of the attribute on each object that contains the score for the chart. e.g. 'num_visits'
9 |
10 | * name_attr - The name of the attribute on each object to be used for its display value. Default is 'name'.
11 |
12 | * in_card - Boolean, default is False. Is this displayed within a .card element?
13 |
14 | * use_cite - If True, then wrap the name in tags. Default: False.
15 | {% endcomment %}
16 |
17 | {% load spectator_core %}
18 |
19 |
20 | {% for obj in object_list %}
21 |
26 | {% endif %}
27 |
--------------------------------------------------------------------------------
/src/spectator/core/templates/spectator_core/includes/thumbnail_detail.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Displays an image for a page like publication_detail or event_detail.
3 |
4 | Expects:
5 | * obj - The object whose thumbnail we're showing.
6 | * alt_text - The text to use as the image's `alt` text.
7 | {% endcomment %}
8 |
9 | {% with link_url=url|default:obj.thumbnail.url %}
10 |
11 |
12 |
13 | {% endwith %}
14 |
--------------------------------------------------------------------------------
/src/spectator/core/templates/spectator_core/includes/thumbnail_list.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Displays an image for use in a list of things (publication, events) etc.
3 |
4 | Expects:
5 | * obj - The object whose thumbnail we're showing.
6 | * alt_text - The text to use as the image's `alt` text.
7 | {% endcomment %}
8 |
9 | {% with link_url=url|default:obj.thumbnail.url %}
10 |
11 |
12 |
13 | {% endwith %}
14 |
--------------------------------------------------------------------------------
/src/spectator/core/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philgyford/django-spectator/1541e54c136acef9b249b5583601207112e913be/src/spectator/core/templatetags/__init__.py
--------------------------------------------------------------------------------
/src/spectator/core/urls/__init__.py:
--------------------------------------------------------------------------------
1 | from django.urls import include, path
2 |
3 | from spectator.core.apps import spectator_apps
4 |
5 | # The aim of this is to:
6 | # a) Make it easy to include Spectator's URLs in a project with a single line.
7 | # b) Also make it easy to only include parts of the project, if necessary.
8 |
9 | app_name = "spectator"
10 |
11 | urlpatterns = [
12 | path("", include("spectator.core.urls.core")),
13 | path("creators/", include("spectator.core.urls.creators")),
14 | ]
15 |
16 | if spectator_apps.is_enabled("events"):
17 | urlpatterns.append(
18 | path("events/", include("spectator.events.urls", namespace="events")),
19 | )
20 |
21 | if spectator_apps.is_enabled("reading"):
22 | urlpatterns.append(
23 | path("reading/", include("spectator.reading.urls", namespace="reading")),
24 | )
25 |
--------------------------------------------------------------------------------
/src/spectator/core/urls/core.py:
--------------------------------------------------------------------------------
1 | from django.urls import path
2 |
3 | from spectator.core import views
4 |
5 | # Only the home page.
6 | # This should be under the namespace 'spectator:core'.
7 |
8 | app_name = "core"
9 |
10 | urlpatterns = [
11 | path("", view=views.HomeView.as_view(), name="home"),
12 | ]
13 |
--------------------------------------------------------------------------------
/src/spectator/core/urls/creators.py:
--------------------------------------------------------------------------------
1 | from django.urls import path, re_path
2 |
3 | from spectator.core import views
4 |
5 | app_name = "creators"
6 |
7 | urlpatterns = [
8 | # Individuals:
9 | path("", view=views.CreatorListView.as_view(), name="creator_list"),
10 | # Groups:
11 | path(
12 | "groups/",
13 | view=views.CreatorListView.as_view(),
14 | name="creator_list_group",
15 | kwargs={"kind": "group"},
16 | ),
17 | re_path(
18 | r"^(?P[\w-]+)/$",
19 | view=views.CreatorDetailView.as_view(),
20 | name="creator_detail",
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/src/spectator/core/utils.py:
--------------------------------------------------------------------------------
1 | from django.utils.html import strip_tags
2 | from django.utils.text import Truncator
3 |
4 |
5 | def truncate_string(
6 | text, *, strip_html=True, chars=255, truncate="…", at_word_boundary=False
7 | ):
8 | """Truncate a string to a certain length, removing line breaks and mutliple
9 | spaces, optionally removing HTML, and appending a 'truncate' string.
10 |
11 | Keyword arguments:
12 | strip_html -- boolean.
13 | chars -- Number of characters to return.
14 | at_word_boundary -- Only truncate at a word boundary, which will probably
15 | result in a string shorter than chars.
16 | truncate -- String to add to the end.
17 | """
18 | if strip_html:
19 | text = strip_tags(text)
20 | text = text.replace("\n", " ").replace("\r", "")
21 | text = " ".join(text.split())
22 | if at_word_boundary:
23 | if len(text) > chars:
24 | text = text[:chars].rsplit(" ", 1)[0] + truncate
25 | else:
26 | text = Truncator(text).chars(chars, html=False, truncate=truncate)
27 | return text
28 |
29 |
30 | def chartify(qs, score_field, *, cutoff=0, ensure_chartiness=True):
31 | """
32 | Given a QuerySet it will go through and add a `chart_position` property to
33 | each object returning a list of the objects.
34 |
35 | If adjacent objects have the same 'score' (based on `score_field`) then
36 | they will have the same `chart_position`. This can then be used in
37 | templates for the `value` of
elements in an .
38 |
39 | By default any objects with a score of 0 or less will be removed.
40 |
41 | By default, if all the items in the chart have the same position, no items
42 | will be returned (it's not much of a chart).
43 |
44 | Keyword arguments:
45 | qs -- The QuerySet
46 | score_field -- The name of the numeric field that each object in the
47 | QuerySet has, that will be used to compare their positions.
48 | cutoff -- Any objects with a score of this value or below will be removed
49 | from the list. Set to None to disable this.
50 | ensure_chartiness -- If True, then if all items in the list have the same
51 | score, an empty list will be returned.
52 | """
53 | chart = []
54 | position = 0
55 | prev_obj = None
56 |
57 | for counter, obj in enumerate(qs):
58 | score = getattr(obj, score_field)
59 |
60 | if score != getattr(prev_obj, score_field, None):
61 | position = counter + 1
62 |
63 | if cutoff is None or score > cutoff:
64 | obj.chart_position = position
65 | chart.append(obj)
66 |
67 | prev_obj = obj
68 |
69 | if (
70 | ensure_chartiness
71 | and len(chart) > 0
72 | and getattr(chart[0], score_field) == getattr(chart[-1], score_field)
73 | ):
74 | chart = []
75 |
76 | return chart
77 |
--------------------------------------------------------------------------------
/src/spectator/events/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = "spectator.events.apps.SpectatorEventsAppConfig"
2 |
--------------------------------------------------------------------------------
/src/spectator/events/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class SpectatorEventsAppConfig(AppConfig):
5 | label = "spectator_events"
6 | name = "spectator.events"
7 | verbose_name = "Spectator Events"
8 |
9 | # Maintain pre Django 3.2 default behaviour:
10 | default_auto_field = "django.db.models.AutoField"
11 |
12 | def ready(self):
13 | import spectator.events.signals # noqa: F401
14 |
--------------------------------------------------------------------------------
/src/spectator/events/factories.py:
--------------------------------------------------------------------------------
1 | import factory
2 |
3 | from spectator.core.factories import IndividualCreatorFactory
4 |
5 | from . import models
6 |
7 |
8 | class VenueFactory(factory.django.DjangoModelFactory):
9 | class Meta:
10 | model = models.Venue
11 |
12 | name = factory.Sequence(lambda n: f"Venue {n}")
13 |
14 |
15 | class WorkFactory(factory.django.DjangoModelFactory):
16 | class Meta:
17 | model = models.Work
18 |
19 | title = factory.Sequence(lambda n: f"Work {n}")
20 |
21 |
22 | class ClassicalWorkFactory(WorkFactory):
23 | kind = "classicalwork"
24 | title = factory.Sequence(lambda n: f"Classical Work {n}")
25 |
26 |
27 | class DancePieceFactory(WorkFactory):
28 | kind = "dancepiece"
29 | title = factory.Sequence(lambda n: f"Dance Piece {n}")
30 |
31 |
32 | class ExhibitionFactory(WorkFactory):
33 | kind = "exhibition"
34 | title = factory.Sequence(lambda n: f"Exhibition {n}")
35 |
36 |
37 | class MovieFactory(WorkFactory):
38 | kind = "movie"
39 | title = factory.Sequence(lambda n: f"Movie {n}")
40 |
41 |
42 | class PlayFactory(WorkFactory):
43 | kind = "play"
44 | title = factory.Sequence(lambda n: f"Play {n}")
45 |
46 |
47 | class WorkRoleFactory(factory.django.DjangoModelFactory):
48 | class Meta:
49 | model = models.WorkRole
50 |
51 | role_name = factory.Sequence(lambda n: f"Role {n}")
52 | creator = factory.SubFactory(IndividualCreatorFactory)
53 | work = factory.SubFactory(WorkFactory)
54 |
55 |
56 | class EventFactory(factory.django.DjangoModelFactory):
57 | class Meta:
58 | model = models.Event
59 |
60 | title = factory.Sequence(lambda n: f"Event {n}")
61 | venue = factory.SubFactory(VenueFactory)
62 | # Bigger width/height than default detail_thumbnail_2x size:
63 | thumbnail = factory.django.ImageField(color="blue", width=800, height=800)
64 |
65 |
66 | class ComedyEventFactory(EventFactory):
67 | kind = "comedy"
68 |
69 |
70 | class ConcertEventFactory(EventFactory):
71 | kind = "concert"
72 |
73 |
74 | class DanceEventFactory(EventFactory):
75 | kind = "dance"
76 |
77 |
78 | class MuseumEventFactory(EventFactory):
79 | kind = "museum"
80 |
81 |
82 | class GigEventFactory(EventFactory):
83 | kind = "gig"
84 |
85 |
86 | class MiscEventFactory(EventFactory):
87 | kind = "misc"
88 |
89 |
90 | class CinemaEventFactory(EventFactory):
91 | kind = "cinema"
92 |
93 |
94 | class TheatreEventFactory(EventFactory):
95 | kind = "theatre"
96 |
97 |
98 | class EventRoleFactory(factory.django.DjangoModelFactory):
99 | class Meta:
100 | model = models.EventRole
101 |
102 | role_name = factory.Sequence(lambda n: f"Role {n}")
103 | creator = factory.SubFactory(IndividualCreatorFactory)
104 | event = factory.SubFactory(MiscEventFactory)
105 |
106 |
107 | class WorkSelectionFactory(factory.django.DjangoModelFactory):
108 | class Meta:
109 | model = models.WorkSelection
110 |
111 | event = factory.SubFactory(MiscEventFactory)
112 | work = factory.SubFactory(WorkFactory)
113 |
--------------------------------------------------------------------------------
/src/spectator/events/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philgyford/django-spectator/1541e54c136acef9b249b5583601207112e913be/src/spectator/events/management/__init__.py
--------------------------------------------------------------------------------
/src/spectator/events/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philgyford/django-spectator/1541e54c136acef9b249b5583601207112e913be/src/spectator/events/management/commands/__init__.py
--------------------------------------------------------------------------------
/src/spectator/events/management/commands/generate_letterboxd_export.py:
--------------------------------------------------------------------------------
1 | import csv
2 |
3 | from django.core.management.base import BaseCommand, CommandError
4 |
5 | from spectator.events.models import Event
6 |
7 |
8 | class Command(BaseCommand):
9 | """
10 | Generates a CSV file containing information about Works of kind
11 | "movie" that have been seen, suitable for importing into a
12 | Letterboxd.com account.
13 |
14 | Import docs: https://letterboxd.com/about/importing-data/
15 | """
16 |
17 | help = (
18 | "Generates a CSV file of watched movies suitable for import into Letterboxd.com"
19 | )
20 |
21 | filename = "watched_movies.csv"
22 |
23 | def handle(self, *args, **options):
24 | "This is called when the command is run."
25 | rows = self.make_rows()
26 |
27 | if len(rows) == 0:
28 | msg = "No movies were found."
29 | raise CommandError(msg)
30 |
31 | self.write_csv_file(rows)
32 |
33 | plural = "movie" if len(rows) == 1 else "movies"
34 | self.stdout.write(
35 | self.style.SUCCESS(f"Wrote {len(rows)} {plural} to {self.filename}")
36 | )
37 |
38 | def make_rows(self):
39 | """
40 | Returns a list of dicts, each one about a single viewing of
41 | a movie.
42 | """
43 | rows = []
44 | watched_work_ids = []
45 |
46 | for event in Event.objects.filter(kind="cinema").order_by("date"):
47 | for selection in event.get_movies():
48 | work = selection.work
49 | is_rewatch = work.id in watched_work_ids
50 |
51 | directors = []
52 | for role in work.roles.all():
53 | if role.role_name.lower() == "director":
54 | directors.append(role.creator.name)
55 |
56 | rows.append(
57 | {
58 | "imdbID": work.imdb_id,
59 | "Title": work.title,
60 | "Year": work.year,
61 | "Directors": ", ".join(directors),
62 | "WatchedDate": event.date.strftime("%Y-%m-%d"),
63 | "Rewatch": str(is_rewatch).lower(),
64 | }
65 | )
66 |
67 | watched_work_ids.append(work.id)
68 |
69 | return rows
70 |
71 | def write_csv_file(self, rows):
72 | """
73 | Passed a list of dicts - each one being data about single
74 | viewing of a movie - writes this out to a CSV file.
75 | """
76 | with open(self.filename, mode="w") as movies_file:
77 | writer = csv.DictWriter(
78 | movies_file,
79 | fieldnames=rows[0].keys(),
80 | delimiter=",",
81 | quotechar='"',
82 | quoting=csv.QUOTE_MINIMAL,
83 | )
84 |
85 | writer.writeheader()
86 |
87 | for row in rows:
88 | writer.writerow(row)
89 |
--------------------------------------------------------------------------------
/src/spectator/events/managers.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.db.models import Count
3 |
4 |
5 | class VenueManager(models.Manager):
6 | def by_visits(self, event_kind=None):
7 | """
8 | Gets Venues in order of how many Events have been held there.
9 | Adds a `num_visits` field to each one.
10 |
11 | event_kind filters by kind of Event, e.g. 'theatre', 'cinema', etc.
12 | """
13 | qs = self.get_queryset()
14 |
15 | if event_kind is not None:
16 | qs = qs.filter(event__kind=event_kind)
17 |
18 | qs = qs.annotate(num_visits=Count("event")).order_by("-num_visits", "name_sort")
19 |
20 | return qs
21 |
22 |
23 | class WorkManager(models.Manager):
24 | def by_views(self, kind=None):
25 | """
26 | Gets Works in order of how many times they've been attached to
27 | Events.
28 |
29 | kind is the kind of Work, e.g. 'play', 'movie', etc.
30 | """
31 | qs = self.get_queryset()
32 |
33 | if kind is not None:
34 | qs = qs.filter(kind=kind)
35 |
36 | qs = qs.annotate(num_views=Count("event")).order_by("-num_views", "title_sort")
37 |
38 | return qs
39 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0002_event_slug.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 1.11 on 2017-11-01 16:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0001_initial"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="event",
15 | name="slug",
16 | field=models.SlugField(blank=True, default="a", max_length=10),
17 | preserve_default=False,
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0003_auto_20171101_1645.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 1.11 on 2017-11-01 16:45
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0002_event_slug"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="classicalwork",
15 | name="slug",
16 | field=models.SlugField(blank=True, default="a", max_length=10),
17 | preserve_default=False,
18 | ),
19 | migrations.AddField(
20 | model_name="dancepiece",
21 | name="slug",
22 | field=models.SlugField(blank=True, default="a", max_length=10),
23 | preserve_default=False,
24 | ),
25 | migrations.AddField(
26 | model_name="movie",
27 | name="slug",
28 | field=models.SlugField(blank=True, default="a", max_length=10),
29 | preserve_default=False,
30 | ),
31 | migrations.AddField(
32 | model_name="play",
33 | name="slug",
34 | field=models.SlugField(blank=True, default="a", max_length=10),
35 | preserve_default=False,
36 | ),
37 | ]
38 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0004_venue_slug.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 1.11 on 2017-11-01 17:32
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0003_auto_20171101_1645"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="venue",
15 | name="slug",
16 | field=models.SlugField(blank=True, default="a", max_length=10),
17 | preserve_default=False,
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0005_auto_20180102_0959.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-02 09:59
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("spectator_events", "0004_venue_slug"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="event",
16 | name="movie",
17 | field=models.ForeignKey(
18 | blank=True,
19 | help_text="Only used if event is of 'Movie' kind.",
20 | null=True,
21 | on_delete=django.db.models.deletion.SET_NULL,
22 | to="spectator_events.Movie",
23 | ),
24 | ),
25 | migrations.AlterField(
26 | model_name="event",
27 | name="play",
28 | field=models.ForeignKey(
29 | blank=True,
30 | help_text="Only used if event is of 'Play' kind.",
31 | null=True,
32 | on_delete=django.db.models.deletion.SET_NULL,
33 | to="spectator_events.Play",
34 | ),
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0006_event_slug_20180102_1127.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-02 11:27
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | from hashids import Hashids
6 |
7 |
8 | def generate_slug(value):
9 | "A copy of spectator.core.models.SluggedModelMixin._generate_slug()"
10 | alphabet = "abcdefghijkmnopqrstuvwxyz23456789"
11 | salt = "Django Spectator"
12 |
13 | if hasattr(settings, "SPECTATOR_SLUG_ALPHABET"):
14 | alphabet = settings.SPECTATOR_SLUG_ALPHABET
15 |
16 | if hasattr(settings, "SPECTATOR_SLUG_SALT"):
17 | salt = settings.SPECTATOR_SLUG_SALT
18 |
19 | hashids = Hashids(alphabet=alphabet, salt=salt, min_length=5)
20 |
21 | return hashids.encode(value)
22 |
23 |
24 | def set_slug(apps, schema_editor):
25 | """
26 | Create a slug for each Event already in the DB.
27 | """
28 | Event = apps.get_model("spectator_events", "Event")
29 |
30 | for e in Event.objects.all():
31 | e.slug = generate_slug(e.pk)
32 | e.save(update_fields=["slug"])
33 |
34 |
35 | class Migration(migrations.Migration):
36 |
37 | dependencies = [
38 | ("spectator_events", "0005_auto_20180102_0959"),
39 | ]
40 |
41 | operations = [
42 | migrations.AlterField(
43 | model_name="event",
44 | name="slug",
45 | field=models.SlugField(blank=True, default="a", max_length=10),
46 | preserve_default=False,
47 | ),
48 | migrations.RunPython(set_slug),
49 | ]
50 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0007_work_slug_20180102_1137.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-02 11:37
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | from hashids import Hashids
6 |
7 |
8 | def generate_slug(value):
9 | "A copy of spectator.core.models.SluggedModelMixin._generate_slug()"
10 | alphabet = "abcdefghijkmnopqrstuvwxyz23456789"
11 | salt = "Django Spectator"
12 |
13 | if hasattr(settings, "SPECTATOR_SLUG_ALPHABET"):
14 | alphabet = settings.SPECTATOR_SLUG_ALPHABET
15 |
16 | if hasattr(settings, "SPECTATOR_SLUG_SALT"):
17 | salt = settings.SPECTATOR_SLUG_SALT
18 |
19 | hashids = Hashids(alphabet=alphabet, salt=salt, min_length=5)
20 |
21 | return hashids.encode(value)
22 |
23 |
24 | def set_slug(apps, schema_editor, class_name):
25 | """
26 | Create a slug for each Work already in the DB.
27 | """
28 | Cls = apps.get_model("spectator_events", class_name)
29 |
30 | for obj in Cls.objects.all():
31 | obj.slug = generate_slug(obj.pk)
32 | obj.save(update_fields=["slug"])
33 |
34 |
35 | def set_classicalwork_slug(apps, schema_editor):
36 | set_slug(apps, schema_editor, "ClassicalWork")
37 |
38 |
39 | def set_dancepiece_slug(apps, schema_editor):
40 | set_slug(apps, schema_editor, "DancePiece")
41 |
42 |
43 | def set_movie_slug(apps, schema_editor):
44 | set_slug(apps, schema_editor, "Movie")
45 |
46 |
47 | def set_play_slug(apps, schema_editor):
48 | set_slug(apps, schema_editor, "Play")
49 |
50 |
51 | class Migration(migrations.Migration):
52 |
53 | dependencies = [
54 | ("spectator_events", "0006_event_slug_20180102_1127"),
55 | ]
56 |
57 | operations = [
58 | migrations.AlterField(
59 | model_name="classicalwork",
60 | name="slug",
61 | field=models.SlugField(blank=True, default="a", max_length=10),
62 | preserve_default=False,
63 | ),
64 | migrations.RunPython(set_classicalwork_slug),
65 | migrations.AlterField(
66 | model_name="dancepiece",
67 | name="slug",
68 | field=models.SlugField(blank=True, default="a", max_length=10),
69 | preserve_default=False,
70 | ),
71 | migrations.RunPython(set_dancepiece_slug),
72 | migrations.AlterField(
73 | model_name="movie",
74 | name="slug",
75 | field=models.SlugField(blank=True, default="a", max_length=10),
76 | preserve_default=False,
77 | ),
78 | migrations.RunPython(set_movie_slug),
79 | migrations.AlterField(
80 | model_name="play",
81 | name="slug",
82 | field=models.SlugField(blank=True, default="a", max_length=10),
83 | preserve_default=False,
84 | ),
85 | migrations.RunPython(set_play_slug),
86 | ]
87 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0008_venue_slug_20180102_1147.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-02 11:47
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | from hashids import Hashids
6 |
7 |
8 | def generate_slug(value):
9 | "A copy of spectator.core.models.SluggedModelMixin._generate_slug()"
10 | alphabet = "abcdefghijkmnopqrstuvwxyz23456789"
11 | salt = "Django Spectator"
12 |
13 | if hasattr(settings, "SPECTATOR_SLUG_ALPHABET"):
14 | alphabet = settings.SPECTATOR_SLUG_ALPHABET
15 |
16 | if hasattr(settings, "SPECTATOR_SLUG_SALT"):
17 | salt = settings.SPECTATOR_SLUG_SALT
18 |
19 | hashids = Hashids(alphabet=alphabet, salt=salt, min_length=5)
20 |
21 | return hashids.encode(value)
22 |
23 |
24 | def set_slug(apps, schema_editor):
25 | """
26 | Create a slug for each Venue already in the DB.
27 | """
28 | Cls = apps.get_model("spectator_events", "Venue")
29 |
30 | for obj in Cls.objects.all():
31 | obj.slug = generate_slug(obj.pk)
32 | obj.save(update_fields=["slug"])
33 |
34 |
35 | class Migration(migrations.Migration):
36 |
37 | dependencies = [
38 | ("spectator_events", "0007_work_slug_20180102_1137"),
39 | ]
40 |
41 | operations = [
42 | migrations.AlterField(
43 | model_name="venue",
44 | name="slug",
45 | field=models.SlugField(blank=True, default="a", max_length=10),
46 | preserve_default=False,
47 | ),
48 | migrations.RunPython(set_slug),
49 | ]
50 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0009_event_note.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-18 09:03
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0008_venue_slug_20180102_1147"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="event",
15 | name="note",
16 | field=models.TextField(
17 | blank=True,
18 | help_text="Optional. Paragraphs will be surrounded with tags. HTML allowed.", # noqa: E501
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0010_auto_20180118_0906.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-18 09:06
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0009_event_note"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="event",
15 | name="note",
16 | field=models.TextField(
17 | blank=True,
18 | help_text="Optional. Paragraphs will be surrounded with <p></p> tags. HTML allowed.", # noqa: E501
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0011_auto_20180125_1348.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-25 13:48
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0010_auto_20180118_0906"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterModelOptions(
14 | name="classicalwork",
15 | options={"ordering": ("title_sort",), "verbose_name": "classical work"},
16 | ),
17 | migrations.AlterModelOptions(
18 | name="classicalworkrole",
19 | options={
20 | "ordering": ("role_order", "role_name"),
21 | "verbose_name": "classical work role",
22 | },
23 | ),
24 | migrations.AlterModelOptions(
25 | name="dancepiece",
26 | options={"ordering": ("title_sort",), "verbose_name": "dance piece"},
27 | ),
28 | migrations.AlterModelOptions(
29 | name="dancepiecerole",
30 | options={
31 | "ordering": ("role_order", "role_name"),
32 | "verbose_name": "dance piece role",
33 | },
34 | ),
35 | migrations.AlterModelOptions(
36 | name="eventrole",
37 | options={
38 | "ordering": ("role_order", "role_name"),
39 | "verbose_name": "event role",
40 | },
41 | ),
42 | migrations.AlterModelOptions(
43 | name="movierole",
44 | options={
45 | "ordering": ("role_order", "role_name"),
46 | "verbose_name": "movie role",
47 | },
48 | ),
49 | migrations.AlterModelOptions(
50 | name="playrole",
51 | options={
52 | "ordering": ("role_order", "role_name"),
53 | "verbose_name": "play role",
54 | },
55 | ),
56 | ]
57 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0013_copy_classical_and_dance_data.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-25 17:42
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Copy the ClassicalWork and DancePiece data to use the new through models.
9 | """
10 | Event = apps.get_model("spectator_events", "Event")
11 | ClassicalWorkSelection = apps.get_model(
12 | "spectator_events", "ClassicalWorkSelection"
13 | )
14 | DancePieceSelection = apps.get_model("spectator_events", "DancePieceSelection")
15 |
16 | for event in Event.objects.all():
17 |
18 | for work in event.classicalworks.all():
19 | selection = ClassicalWorkSelection(classical_work=work, event=event)
20 | selection.save()
21 |
22 | for piece in event.dancepieces.all():
23 | selection = DancePieceSelection(dance_piece=piece, event=event)
24 | selection.save()
25 |
26 |
27 | class Migration(migrations.Migration):
28 |
29 | dependencies = [
30 | ("spectator_events", "0012_add_classical_and_dance_through_models"),
31 | ]
32 |
33 | operations = [
34 | migrations.RunPython(forwards),
35 | ]
36 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0014_remove_old_classical_dance.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-25 17:56
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 | """
8 | Remove the old ClassicalWork and DancePiece relationships to Events,
9 | now we've copied their data to the new version with a through model.
10 | """
11 |
12 | dependencies = [
13 | ("spectator_events", "0013_copy_classical_and_dance_data"),
14 | ]
15 |
16 | operations = [
17 | migrations.RemoveField(
18 | model_name="event",
19 | name="classicalworks",
20 | ),
21 | migrations.RemoveField(
22 | model_name="event",
23 | name="dancepieces",
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0015_rename_classical_dance_on_event.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-25 17:57
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 | """
8 | Now we've deleted the old classicalworks and dancepieces fields,
9 | rename the new ones to be the same as the old.
10 | """
11 |
12 | dependencies = [
13 | ("spectator_events", "0014_remove_old_classical_dance"),
14 | ]
15 |
16 | operations = [
17 | migrations.RemoveField(
18 | model_name="event",
19 | name="classicalworks2",
20 | ),
21 | migrations.RemoveField(
22 | model_name="event",
23 | name="dancepieces2",
24 | ),
25 | migrations.AddField(
26 | model_name="event",
27 | name="classicalworks",
28 | field=models.ManyToManyField(
29 | blank=True,
30 | help_text="Only used if event is of 'Classical Concert' kind.",
31 | through="spectator_events.ClassicalWorkSelection",
32 | to="spectator_events.ClassicalWork",
33 | ),
34 | ),
35 | migrations.AddField(
36 | model_name="event",
37 | name="dancepieces",
38 | field=models.ManyToManyField(
39 | blank=True,
40 | help_text="Only used if event is of 'Dance' kind.",
41 | through="spectator_events.DancePieceSelection",
42 | to="spectator_events.DancePiece",
43 | ),
44 | ),
45 | ]
46 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0017_copy_movies_and_plays_data.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-26 09:09
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forward(apps, schema_editor):
7 | """
8 | Copying data from the old `Event.movie` and `Event.play` ForeignKey fields
9 | into the new `Event.movies` and `Event.plays` ManyToManyFields.
10 | """
11 |
12 | Event = apps.get_model("spectator_events", "Event")
13 | MovieSelection = apps.get_model("spectator_events", "MovieSelection")
14 | PlaySelection = apps.get_model("spectator_events", "PlaySelection")
15 |
16 | for event in Event.objects.all():
17 | if event.movie is not None:
18 | selection = MovieSelection(event=event, movie=event.movie)
19 | selection.save()
20 |
21 | if event.play is not None:
22 | selection = PlaySelection(event=event, play=event.play)
23 | selection.save()
24 |
25 |
26 | class Migration(migrations.Migration):
27 |
28 | dependencies = [
29 | ("spectator_events", "0016_add_movies_plays_m2ms_on_event"),
30 | ]
31 |
32 | operations = [
33 | migrations.RunPython(forward),
34 | ]
35 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0018_remove_old_movie_play_fields.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-26 09:16
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 | """
8 | Now we've copied their data to the new movies and plays m2m fields,
9 | we can remove the old movie and play fk fields.
10 | """
11 |
12 | dependencies = [
13 | ("spectator_events", "0017_copy_movies_and_plays_data"),
14 | ]
15 |
16 | operations = [
17 | migrations.RemoveField(
18 | model_name="event",
19 | name="movie",
20 | ),
21 | migrations.RemoveField(
22 | model_name="event",
23 | name="play",
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0019_auto_20180127_1653.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-27 16:53
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0018_remove_old_movie_play_fields"),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name="classicalworkselection",
15 | old_name="classical_work",
16 | new_name="work",
17 | ),
18 | migrations.RenameField(
19 | model_name="dancepieceselection",
20 | old_name="dance_piece",
21 | new_name="work",
22 | ),
23 | migrations.RenameField(
24 | model_name="movieselection",
25 | old_name="movie",
26 | new_name="work",
27 | ),
28 | migrations.RenameField(
29 | model_name="playselection",
30 | old_name="play",
31 | new_name="work",
32 | ),
33 | ]
34 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0020_venue_note.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-29 17:32
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0019_auto_20180127_1653"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="venue",
15 | name="note",
16 | field=models.TextField(
17 | blank=True,
18 | help_text="Optional. Paragraphs will be surrounded with <p></p> tags. HTML allowed.", # noqa: E501
19 | ),
20 | ),
21 | ]
22 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0021_auto_20180129_1735.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-29 17:35
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("spectator_events", "0020_venue_note"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="event",
16 | name="venue",
17 | field=models.ForeignKey(
18 | blank=True,
19 | on_delete=django.db.models.deletion.CASCADE,
20 | to="spectator_events.Venue",
21 | ),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0022_auto_20180129_1752.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-29 17:52
2 |
3 | import django.db.models.deletion
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("spectator_events", "0021_auto_20180129_1735"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="event",
16 | name="venue",
17 | field=models.ForeignKey(
18 | blank=True,
19 | null=True,
20 | on_delete=django.db.models.deletion.CASCADE,
21 | to="spectator_events.Venue",
22 | ),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0023_venue_cinema_treasures_id.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-31 14:35
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0022_auto_20180129_1752"),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name="venue",
15 | name="cinema_treasures_id",
16 | field=models.PositiveSmallIntegerField(
17 | blank=True,
18 | help_text='Optional. ID of a cinema at Cinema Treasures.', # noqa: E501
19 | null=True,
20 | ),
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0024_event_venue_name.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-31 15:37
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Set the venue_name field of all Events that have a Venue.
9 | """
10 | Event = apps.get_model("spectator_events", "Event")
11 |
12 | for event in Event.objects.all():
13 | if event.venue is not None:
14 | event.venue_name = event.venue.name
15 | event.save()
16 |
17 |
18 | class Migration(migrations.Migration):
19 |
20 | dependencies = [
21 | ("spectator_events", "0023_venue_cinema_treasures_id"),
22 | ]
23 |
24 | operations = [
25 | migrations.AddField(
26 | model_name="event",
27 | name="venue_name",
28 | field=models.CharField(
29 | blank=True,
30 | help_text="The name of the Venue when this event occurred. If left blank, will be set automatically.", # noqa: E501
31 | max_length=255,
32 | ),
33 | ),
34 | migrations.RunPython(forwards),
35 | ]
36 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0025_auto_20180131_1755.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-01-31 17:55
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0024_event_venue_name"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="venue",
15 | name="cinema_treasures_id",
16 | field=models.PositiveIntegerField(
17 | blank=True,
18 | help_text='Optional. ID of a cinema at Cinema Treasures.', # noqa: E501
19 | null=True,
20 | ),
21 | ),
22 | ]
23 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0027_classicalworks_to_works.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 11:32
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Change all ClassicalWork objects into Work objects, and their associated
9 | data into WorkRole and WorkSelection models, then delete the ClassicalWork.
10 | """
11 | ClassicalWork = apps.get_model("spectator_events", "ClassicalWork")
12 | Work = apps.get_model("spectator_events", "Work")
13 | WorkRole = apps.get_model("spectator_events", "WorkRole")
14 | WorkSelection = apps.get_model("spectator_events", "WorkSelection")
15 |
16 | for cw in ClassicalWork.objects.all():
17 |
18 | work = Work.objects.create(
19 | kind="classicalwork", title=cw.title, title_sort=cw.title_sort
20 | )
21 |
22 | for role in cw.roles.all():
23 | WorkRole.objects.create(
24 | creator=role.creator,
25 | work=work,
26 | role_name=role.role_name,
27 | role_order=role.role_order,
28 | )
29 |
30 | for selection in cw.events.all():
31 | WorkSelection.objects.create(
32 | event=selection.event, work=work, order=selection.order
33 | )
34 |
35 | cw.delete()
36 |
37 |
38 | class Migration(migrations.Migration):
39 |
40 | dependencies = [
41 | ("spectator_events", "0026_auto_20180208_1126"),
42 | ]
43 |
44 | operations = [
45 | migrations.RunPython(forwards),
46 | ]
47 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0028_dancepieces_to_works.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 11:45
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Change all DancePiece objects into Work objects, and their associated
9 | data into WorkRole and WorkSelection models, then delete the DancePiece.
10 | """
11 | DancePiece = apps.get_model("spectator_events", "DancePiece")
12 | Work = apps.get_model("spectator_events", "Work")
13 | WorkRole = apps.get_model("spectator_events", "WorkRole")
14 | WorkSelection = apps.get_model("spectator_events", "WorkSelection")
15 |
16 | for dp in DancePiece.objects.all():
17 |
18 | work = Work.objects.create(
19 | kind="dancepiece", title=dp.title, title_sort=dp.title_sort
20 | )
21 |
22 | for role in dp.roles.all():
23 | WorkRole.objects.create(
24 | creator=role.creator,
25 | work=work,
26 | role_name=role.role_name,
27 | role_order=role.role_order,
28 | )
29 |
30 | for selection in dp.events.all():
31 | WorkSelection.objects.create(
32 | event=selection.event, work=work, order=selection.order
33 | )
34 |
35 | dp.delete()
36 |
37 |
38 | class Migration(migrations.Migration):
39 |
40 | dependencies = [
41 | ("spectator_events", "0027_classicalworks_to_works"),
42 | ]
43 |
44 | operations = [
45 | migrations.RunPython(forwards),
46 | ]
47 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0029_plays_to_works.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 11:47
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Change all Play objects into Work objects, and their associated
9 | data into WorkRole and WorkSelection models, then delete the Play.
10 | """
11 | Play = apps.get_model("spectator_events", "Play")
12 | Work = apps.get_model("spectator_events", "Work")
13 | WorkRole = apps.get_model("spectator_events", "WorkRole")
14 | WorkSelection = apps.get_model("spectator_events", "WorkSelection")
15 |
16 | for p in Play.objects.all():
17 |
18 | work = Work.objects.create(kind="play", title=p.title, title_sort=p.title_sort)
19 |
20 | for role in p.roles.all():
21 | WorkRole.objects.create(
22 | creator=role.creator,
23 | work=work,
24 | role_name=role.role_name,
25 | role_order=role.role_order,
26 | )
27 |
28 | for selection in p.events.all():
29 | WorkSelection.objects.create(
30 | event=selection.event, work=work, order=selection.order
31 | )
32 |
33 | p.delete()
34 |
35 |
36 | class Migration(migrations.Migration):
37 |
38 | dependencies = [
39 | ("spectator_events", "0028_dancepieces_to_works"),
40 | ]
41 |
42 | operations = [
43 | migrations.RunPython(forwards),
44 | ]
45 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0030_movies_to_works.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 11:49
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Change all Movie objects into Work objects, and their associated
9 | data into WorkRole and WorkSelection models, then delete the Movie.
10 | """
11 | Movie = apps.get_model("spectator_events", "Movie")
12 | Work = apps.get_model("spectator_events", "Work")
13 | WorkRole = apps.get_model("spectator_events", "WorkRole")
14 | WorkSelection = apps.get_model("spectator_events", "WorkSelection")
15 |
16 | for m in Movie.objects.all():
17 |
18 | work = Work.objects.create(
19 | kind="movie",
20 | title=m.title,
21 | title_sort=m.title_sort,
22 | year=m.year,
23 | imdb_id=m.imdb_id,
24 | )
25 |
26 | for role in m.roles.all():
27 | WorkRole.objects.create(
28 | creator=role.creator,
29 | work=work,
30 | role_name=role.role_name,
31 | role_order=role.role_order,
32 | )
33 |
34 | for selection in m.events.all():
35 | WorkSelection.objects.create(
36 | event=selection.event, work=work, order=selection.order
37 | )
38 |
39 | m.delete()
40 |
41 |
42 | class Migration(migrations.Migration):
43 |
44 | dependencies = [
45 | ("spectator_events", "0029_plays_to_works"),
46 | ]
47 |
48 | operations = [
49 | migrations.RunPython(forwards),
50 | ]
51 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0031_auto_20180208_1412.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 14:12
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0030_movies_to_works"),
10 | ]
11 |
12 | operations = [
13 | migrations.RemoveField(
14 | model_name="event",
15 | name="classicalworks",
16 | ),
17 | migrations.RemoveField(
18 | model_name="event",
19 | name="dancepieces",
20 | ),
21 | migrations.RemoveField(
22 | model_name="event",
23 | name="movies",
24 | ),
25 | migrations.RemoveField(
26 | model_name="event",
27 | name="plays",
28 | ),
29 | migrations.AddField(
30 | model_name="event",
31 | name="works",
32 | field=models.ManyToManyField(
33 | blank=True,
34 | through="spectator_events.WorkSelection",
35 | to="spectator_events.Work",
36 | ),
37 | ),
38 | ]
39 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0032_recreate_work_slugs.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 14:17
2 |
3 | from django.conf import settings
4 | from django.db import migrations
5 | from hashids import Hashids
6 |
7 |
8 | def generate_slug(value):
9 | """
10 | Generates a slug using a Hashid of `value`.
11 |
12 | Taken from spectator_core.models.SluggedModelMixin
13 | """
14 | # Defaults:
15 | alphabet = "abcdefghijkmnopqrstuvwxyz23456789"
16 | salt = "Django Spectator"
17 |
18 | if hasattr(settings, "SPECTATOR_SLUG_ALPHABET"):
19 | alphabet = settings.SPECTATOR_SLUG_ALPHABET
20 |
21 | if hasattr(settings, "SPECTATOR_SLUG_SALT"):
22 | salt = settings.SPECTATOR_SLUG_SALT
23 |
24 | hashids = Hashids(alphabet=alphabet, salt=salt, min_length=5)
25 |
26 | return hashids.encode(value)
27 |
28 |
29 | def forwards(apps, schema_editor):
30 | """
31 | Re-save all the Works because something earlier didn't create their slugs.
32 | """
33 | Work = apps.get_model("spectator_events", "Work")
34 |
35 | for work in Work.objects.all():
36 | if not work.slug:
37 | work.slug = generate_slug(work.pk)
38 | work.save()
39 |
40 |
41 | class Migration(migrations.Migration):
42 |
43 | dependencies = [
44 | ("spectator_events", "0031_auto_20180208_1412"),
45 | ]
46 |
47 | operations = [
48 | migrations.RunPython(forwards),
49 | ]
50 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0034_auto_20180208_1618.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-08 16:18
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0033_auto_20180208_1613"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="event",
15 | name="kind",
16 | field=models.CharField(
17 | choices=[
18 | ("concert", "Classical"),
19 | ("comedy", "Comedy"),
20 | ("dance", "Dance"),
21 | ("exhibition", "Exhibition"),
22 | ("gig", "Gig"),
23 | ("misc", "Other"),
24 | ("movie", "Movie"),
25 | ("play", "Theatre"),
26 | ],
27 | help_text="Used to categorise event. But any kind of Work can be added to any kind of Event.", # noqa: E501
28 | max_length=20,
29 | ),
30 | ),
31 | ]
32 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0035_change_event_kinds.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-02-14 13:24
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Change Events with kind 'movie' to 'cinema'
9 | and Events with kind 'play' to 'theatre'.
10 |
11 | Purely for more consistency.
12 | """
13 | Event = apps.get_model("spectator_events", "Event")
14 |
15 | for ev in Event.objects.filter(kind="movie"):
16 | ev.kind = "cinema"
17 | ev.save()
18 |
19 | for ev in Event.objects.filter(kind="play"):
20 | ev.kind = "theatre"
21 | ev.save()
22 |
23 |
24 | class Migration(migrations.Migration):
25 |
26 | dependencies = [
27 | ("spectator_events", "0034_auto_20180208_1618"),
28 | ]
29 |
30 | operations = [
31 | migrations.RunPython(forwards),
32 | ]
33 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0036_auto_20180417_1218.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-04-17 12:18
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0035_change_event_kinds"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="event",
15 | name="kind",
16 | field=models.CharField(
17 | choices=[
18 | ("cinema", "Cinema"),
19 | ("concert", "Concert"),
20 | ("comedy", "Comedy"),
21 | ("dance", "Dance"),
22 | ("exhibition", "Exhibition"),
23 | ("museum", "Gallery/Museum"),
24 | ("gig", "Gig"),
25 | ("theatre", "Theatre"),
26 | ("misc", "Other"),
27 | ],
28 | help_text="Used to categorise event. But any kind of Work can be added to any kind of Event.", # noqa: E501
29 | max_length=20,
30 | ),
31 | ),
32 | ]
33 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0037_exhibition_to_museum.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-04-17 12:18
2 |
3 | from django.db import migrations
4 |
5 |
6 | def forwards(apps, schema_editor):
7 | """
8 | Migrate all 'exhibition' Events to the new 'museum' Event kind.
9 | """
10 | Event = apps.get_model("spectator_events", "Event")
11 |
12 | for ev in Event.objects.filter(kind="exhibition"):
13 | ev.kind = "museum"
14 | ev.save()
15 |
16 |
17 | class Migration(migrations.Migration):
18 |
19 | dependencies = [
20 | ("spectator_events", "0036_auto_20180417_1218"),
21 | ]
22 |
23 | operations = [
24 | migrations.RunPython(forwards),
25 | ]
26 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0038_auto_20180417_1224.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-04-17 12:24
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0037_exhibition_to_museum"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="event",
15 | name="kind",
16 | field=models.CharField(
17 | choices=[
18 | ("cinema", "Cinema"),
19 | ("concert", "Concert"),
20 | ("comedy", "Comedy"),
21 | ("dance", "Dance"),
22 | ("museum", "Gallery/Museum"),
23 | ("gig", "Gig"),
24 | ("theatre", "Theatre"),
25 | ("misc", "Other"),
26 | ],
27 | help_text="Used to categorise event. But any kind of Work can be added to any kind of Event.", # noqa: E501
28 | max_length=20,
29 | ),
30 | ),
31 | ]
32 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0039_populate_exhibitions.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-04-17 12:44
2 |
3 | from django.db import migrations
4 | from hashids import Hashids
5 |
6 | from spectator.core import app_settings
7 |
8 |
9 | def generate_slug(value):
10 | """
11 | Generates a slug using a Hashid of `value`.
12 |
13 | COPIED from spectator.core.models.SluggedModelMixin() because migrations
14 | don't make this happen automatically and perhaps the least bad thing is
15 | to copy the method here, ugh.
16 | """
17 | alphabet = app_settings.SLUG_ALPHABET
18 | salt = app_settings.SLUG_SALT
19 |
20 | hashids = Hashids(alphabet=alphabet, salt=salt, min_length=5)
21 |
22 | return hashids.encode(value)
23 |
24 |
25 | def forwards(apps, schema_editor):
26 | """
27 | Having added the new 'exhibition' Work type, we're going to assume that
28 | every Event of type 'museum' should actually have one Exhibition attached.
29 |
30 | So, we'll add one, with the same title as the Event.
31 | And we'll move all Creators from the Event to the Exhibition.
32 | """
33 | Event = apps.get_model("spectator_events", "Event")
34 | Work = apps.get_model("spectator_events", "Work")
35 | WorkRole = apps.get_model("spectator_events", "WorkRole")
36 | WorkSelection = apps.get_model("spectator_events", "WorkSelection")
37 |
38 | for event in Event.objects.filter(kind="museum"):
39 |
40 | # Create a new Work based on this Event's details.
41 |
42 | work = Work.objects.create(
43 | kind="exhibition", title=event.title, title_sort=event.title_sort
44 | )
45 | # This doesn't generate the slug field automatically because Django.
46 | # So we'll have to do it manually. Graarhhh.
47 | work.slug = generate_slug(work.pk)
48 | work.save()
49 |
50 | # Associate the new Work with the Event.
51 | WorkSelection.objects.create(event=event, work=work)
52 |
53 | # Associate any Creators on the Event with the new Work.
54 | for role in event.roles.all():
55 | WorkRole.objects.create(
56 | creator=role.creator,
57 | work=work,
58 | role_name=role.role_name,
59 | role_order=role.role_order,
60 | )
61 |
62 | # Remove Creators from the Event.
63 | role.delete()
64 |
65 |
66 | class Migration(migrations.Migration):
67 |
68 | dependencies = [
69 | ("spectator_events", "0038_auto_20180417_1224"),
70 | ]
71 |
72 | operations = [
73 | migrations.RunPython(forwards),
74 | ]
75 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0040_auto_20180417_1721.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0 on 2018-04-17 17:21
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0039_populate_exhibitions"),
10 | ]
11 |
12 | operations = [
13 | migrations.AlterField(
14 | model_name="work",
15 | name="kind",
16 | field=models.CharField(
17 | choices=[
18 | ("classicalwork", "Classical work"),
19 | ("dancepiece", "Dance piece"),
20 | ("exhibition", "Exhibition"),
21 | ("movie", "Movie"),
22 | ("play", "Play"),
23 | ],
24 | max_length=20,
25 | ),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0041_event_ticket.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.2.3 on 2019-07-31 12:34
2 |
3 | from django.db import migrations, models
4 |
5 | import spectator.events.models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ("spectator_events", "0040_auto_20180417_1721"),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name="event",
17 | name="ticket",
18 | field=models.ImageField(
19 | blank=True,
20 | default="",
21 | upload_to=spectator.events.models.event_upload_path,
22 | ),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0042_auto_20200407_1039.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-04-07 10:39
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("spectator_events", "0041_event_ticket"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="venue",
16 | name="cinema_treasures_id",
17 | field=models.PositiveIntegerField(
18 | blank=True,
19 | help_text='Optional. ID of a cinema at\nCinema Treasures.', # noqa: E501
20 | null=True,
21 | ),
22 | ),
23 | migrations.AlterField(
24 | model_name="work",
25 | name="imdb_id",
26 | field=models.CharField(
27 | blank=True,
28 | help_text="Starts with 'tt', e.g. 'tt0100842'.\nFrom IMDb.", # noqa: E501
29 | max_length=12,
30 | validators=[
31 | django.core.validators.RegexValidator(
32 | code="invalid_imdb_id",
33 | message='IMDb ID should be like "tt1234567"',
34 | regex="^tt\\d{7,10}$",
35 | )
36 | ],
37 | verbose_name="IMDb ID",
38 | ),
39 | ),
40 | ]
41 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0043_rename_ticket_to_thumbnail.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-04-07 10:49
2 |
3 | from django.db import migrations
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ("spectator_events", "0042_auto_20200407_1039"),
10 | ]
11 |
12 | operations = [
13 | migrations.RenameField(
14 | model_name="event",
15 | old_name="ticket",
16 | new_name="thumbnail",
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0044_change_thumbnail_upload_to_function.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.5 on 2020-04-07 10:51
2 |
3 | from django.db import migrations, models
4 |
5 | import spectator.core.models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ("spectator_events", "0043_rename_ticket_to_thumbnail"),
12 | ]
13 |
14 | operations = [
15 | migrations.AlterField(
16 | model_name="event",
17 | name="thumbnail",
18 | field=models.ImageField(
19 | blank=True,
20 | default="",
21 | upload_to=spectator.core.models.thumbnail_upload_path,
22 | ),
23 | ),
24 | ]
25 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0045_auto_20201221_1358.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.1.3 on 2020-12-21 13:58
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ("spectator_events", "0044_change_thumbnail_upload_to_function"),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name="venue",
16 | name="cinema_treasures_id",
17 | field=models.PositiveIntegerField(
18 | blank=True,
19 | help_text='Optional. ID of a cinema at\n Cinema Treasures.', # noqa: E501
20 | null=True,
21 | ),
22 | ),
23 | migrations.AlterField(
24 | model_name="work",
25 | name="imdb_id",
26 | field=models.CharField(
27 | blank=True,
28 | help_text="Starts with 'tt', e.g. 'tt0100842'.\n From IMDb.", # noqa: E501
29 | max_length=12,
30 | validators=[
31 | django.core.validators.RegexValidator(
32 | code="invalid_imdb_id",
33 | message='IMDb ID should be like "tt1234567"',
34 | regex="^tt\\d{7,10}$",
35 | )
36 | ],
37 | verbose_name="IMDb ID",
38 | ),
39 | ),
40 | ]
41 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/0046_alter_work_imdb_id.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 5.1.4 on 2025-04-22 08:56
2 |
3 | import django.core.validators
4 | from django.db import migrations, models
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ('spectator_events', '0045_auto_20201221_1358'),
11 | ]
12 |
13 | operations = [
14 | migrations.AlterField(
15 | model_name='work',
16 | name='imdb_id',
17 | field=models.CharField(blank=True, help_text='Starts with \'tt\', e.g. \'tt0100842\'.\n From IMDb.', max_length=12, validators=[django.core.validators.RegexValidator(code='invalid_imdb_id', message='IMDb ID should be like "tt1234567"', regex='^tt[0-9]{7,10}$')], verbose_name='IMDb ID'),
18 | ),
19 | ]
20 |
--------------------------------------------------------------------------------
/src/spectator/events/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philgyford/django-spectator/1541e54c136acef9b249b5583601207112e913be/src/spectator/events/migrations/__init__.py
--------------------------------------------------------------------------------
/src/spectator/events/signals.py:
--------------------------------------------------------------------------------
1 | from django.db.models.signals import post_delete, post_save
2 | from django.dispatch import receiver
3 |
4 | from .models import EventRole
5 |
6 |
7 | @receiver(post_delete, sender=EventRole, dispatch_uid="spectator.delete.event_role")
8 | @receiver(post_save, sender=EventRole, dispatch_uid="spectator.save.event_role")
9 | def eventrole_changed(sender, **kwargs):
10 | """
11 | When an Event's creators are changed we want to re-save the Event
12 | itself so that its title_sort can be recreated if necessary
13 | """
14 | kwargs["instance"].event.save()
15 |
--------------------------------------------------------------------------------
/src/spectator/events/sitemaps.py:
--------------------------------------------------------------------------------
1 | from django.contrib.sitemaps import Sitemap
2 |
3 | from .models import Event, Venue, Work
4 |
5 |
6 | class EventSitemap(Sitemap):
7 | changefreq = "never"
8 | priority = 0.5
9 |
10 | def items(self):
11 | # Exclude movies and plays because they'll have the same URLs as their
12 | # Movie and Play objects.
13 | return Event.objects.exclude(kind="movie").exclude(kind="play")
14 |
15 | def lastmod(self, obj):
16 | return obj.time_modified
17 |
18 |
19 | class VenueSitemap(Sitemap):
20 | changefreq = "monthly"
21 | priority = 0.5
22 |
23 | def items(self):
24 | return Venue.objects.all()
25 |
26 | def lastmod(self, obj):
27 | return obj.time_modified
28 |
29 |
30 | class WorkSitemap(Sitemap):
31 | changefreq = "monthly"
32 | priority = 0.5
33 |
34 | def items(self):
35 | return Work.objects.all()
36 |
37 | def lastmod(self, obj):
38 | return obj.time_modified
39 |
--------------------------------------------------------------------------------
/src/spectator/events/static/css/admin/location_picker.css:
--------------------------------------------------------------------------------
1 | .setloc-map {
2 | max-width: 100%;
3 | height: 400px;
4 | margin-top: 1em;
5 | border: 1px solid #eee;
6 | }
7 |
--------------------------------------------------------------------------------
/src/spectator/events/static/img/map-marker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philgyford/django-spectator/1541e54c136acef9b249b5583601207112e913be/src/spectator/events/static/img/map-marker.png
--------------------------------------------------------------------------------
/src/spectator/events/static/js/venue_map.js:
--------------------------------------------------------------------------------
1 | /**
2 | * For displaying the map on the VenueDetail page.
3 | *
4 | * Expects there to be three variables declared:
5 | * spectator_map_latitude
6 | * spectator_map_longitude
7 | * spectator_map_config
8 | *
9 | * And for there to be a div, sized appropriately, with a class of
10 | * 'js-venue-map-container'.
11 | *
12 | * spectator_map_config should be set for one of the supported map libraries,
13 | * and the releveant CSS, and JS for the library should have been included in
14 | * the page. See the Django Spectator docs for more details.
15 | *
16 | * 1. Google
17 | * {
18 | * "library": "google"
19 | * }
20 | *
21 | * 2. Mabox
22 | * {
23 | * "library": "mapbox",
24 | * "tile_style": "mapbox://styles/mapbox/light-v10"
25 | * }
26 | */
27 | function spectatorInitMap() {
28 | var lat =
29 | typeof spectator_map_latitude !== "undefined" ? spectator_map_latitude : "";
30 | var lon =
31 | typeof spectator_map_longitude !== "undefined"
32 | ? spectator_map_longitude
33 | : "";
34 | var mapConfig =
35 | typeof spectator_map_config !== "undefined" ? spectator_map_config : false;
36 |
37 | if (!lat || !lon || !mapConfig) {
38 | return;
39 | }
40 |
41 | // Create the map element.
42 | var container = document.getElementsByClassName("js-venue-map-container")[0];
43 | container.innerHTML = '';
44 |
45 | var mapEl = document.getElementsByClassName("js-venue-map")[0];
46 |
47 | var map;
48 |
49 | // Create the map and marker depending on which library we're using...
50 |
51 | if (mapConfig.library === "google") {
52 | var position = { lat: parseFloat(lat), lng: parseFloat(lon) };
53 |
54 | var tileStyle = mapConfig.tile_style ? mapConfig.tile_style : "roadmap";
55 |
56 | map = new google.maps.Map(mapEl, {
57 | zoom: 12,
58 | center: position,
59 | mapTypeId: tileStyle,
60 | });
61 |
62 | new google.maps.Marker({
63 | map: map,
64 | position: position,
65 | });
66 | } else if (mapConfig.library == "mapbox") {
67 | var position = [parseFloat(lon), parseFloat(lat)];
68 |
69 | var tilesStyle = "mapbox://styles/mapbox/streets-v11";
70 | if (mapConfig.tile_style) {
71 | tileStyle = mapConfig.tile_style;
72 | }
73 |
74 | map = new mapboxgl.Map({
75 | container: mapEl,
76 | center: position,
77 | style: tileStyle,
78 | zoom: 11,
79 | });
80 |
81 | map.addControl(new mapboxgl.NavigationControl());
82 |
83 | // Create and add marker
84 | var el = document.createElement("div");
85 | el.className = "spectator-marker"; // The CSS classname
86 | new mapboxgl.Marker(el, { anchor: "bottom" })
87 | .setLngLat(position)
88 | .addTo(map);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/admin/spectator_events/venue/change_form.html:
--------------------------------------------------------------------------------
1 | {% extends "admin/change_form.html" %}
2 | {% load static %}
3 | {# Overriding default template for the Venue change form, so we can include
4 | the below CSS and JS. #}
5 |
6 | {% block extrastyle %}
7 | {{ block.super }}
8 |
9 | {% if SPECTATOR_MAPS and SPECTATOR_MAPS.enable and SPECTATOR_MAPS.library == "mapbox" %}
10 |
19 | {% endif %}
20 | {% endblock extrastyle %}
21 |
22 |
23 | {% block footer %}
24 | {{ block.super }}
25 |
26 | {% if SPECTATOR_MAPS and SPECTATOR_MAPS.enable %}
27 | {# Best way of putting the config dict into a JS variable. #}
28 | {{ SPECTATOR_MAPS|json_script:"spectator-maps-config" }}
29 |
34 |
35 | {% if SPECTATOR_MAPS.library == "mapbox" %}
36 |
39 | {% endif %}
40 | {% endif %}
41 | {% endblock footer %}
42 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/base.html:
--------------------------------------------------------------------------------
1 | {% extends 'spectator_core/base.html' %}
2 |
3 | {% load spectator_core spectator_events %}
4 |
5 | {% block events_nav_active %}active{% endblock %}
6 |
7 | {% block breadcrumbs %}
8 | {{ block.super }}
9 |
23 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/card_years.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Used by the events_years_card template tag.
3 |
4 | Expects:
5 |
6 | * current_year: A date object representing the current year, if any.
7 | * years: A QuerySet of date objects, one for each year to link to.
8 | {% endcomment %}
9 |
10 | {% if years|length > 0 %}
11 | {% load spectator_core %}
12 | {% current_url_name as url_name %}
13 |
21 | {% endif %}
22 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/events_paginated.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Displays a paginated list of Events.
3 |
4 | Expects:
5 | * event_list - List or Queryset of Events.
6 | * page_obj - A DiggPaginator instance.
7 | {% endcomment %}
8 |
9 | {% if event_list|length > 0 %}
10 | {% if page_obj|default:False and page_obj.number > 1 %}
11 | {% include 'spectator_core/includes/pagination.html' with page_obj=page_obj only %}
12 | {% endif %}
13 |
14 | {% include 'spectator_events/includes/events.html' with event_list=event_list only %}
15 |
16 | {% include 'spectator_core/includes/pagination.html' with page_obj=page_obj only %}
17 |
18 | {% else %}
19 |
There are no events to show.
20 | {% endif %}
21 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/selections.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | For displaying a list of Selections that are attached to an Event. e.g., ClassicalWorkSelections or MovieSelections.
3 |
4 | Used on the Event Detail page.
5 |
6 | Expects:
7 | * selection_list - A list/QuerySet of ClassicalWorkSelections, MovieSelections, etc.
8 | * heading - Optional text to use for the heading. If none, no heading is shown.
9 | {% endcomment %}
10 |
11 | {% if selection_list|length > 0 %}
12 | {% if heading %}
13 |
{{ heading }}
14 | {% endif %}
15 |
16 |
17 | {% for selection in selection_list %}
18 | {% include 'spectator_events/includes/work.html' with work=selection.work only %}
19 | {% endfor %}
20 |
21 | {% endif %}
22 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/visits.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Used for Plays and Movies, listing different Events that item was visited.
3 |
4 | Expects:
5 |
6 | * events - A list or Queryset of Event objects.
7 | * heading_level - The level of heading to use, eg ('h2'). Default is None; no heading.
8 | {% endcomment %}
9 |
10 | {% load spectator_events %}
11 |
12 | {% if events|length > 0 %}
13 | {% if heading_level|default:False %}
14 | <{{ heading_level }}>Viewings{{ heading_level }}>
15 | {% endif %}
16 |
31 | {% endif %}
32 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/work.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | For displaying a single Movie, Concert Work, etc in a list.
3 |
4 | Expects:
5 | * work - A Movie, ConcertWork, DancePiece or Play.
6 | {% endcomment %}
7 |
8 | {% load l10n %}
9 |
10 |
11 | {{ work.title }}
12 | {% if work.kind == 'movie' and work.year %}
13 | ({{ work.year|unlocalize }})
14 | {% endif %}
15 | {% include 'spectator_core/includes/roles.html' with roles=work.roles.all intro=' ' %}
16 |
17 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/works.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | For displaying a list of works that are attached to an Event by a ManyToManyField. e.g., Classical Works or Dance Pieces.
3 |
4 | Expects:
5 | * work_list - A list/QuerySet of ClassicalWorks, Movies, etc.
6 | * heading - Optional text to use for the heading. If none, no heading is shown.
7 | {% endcomment %}
8 |
9 | {% if work_list|length > 0 %}
10 | {% if heading %}
11 |
{{ heading }}
12 | {% endif %}
13 |
14 |
15 | {% for work in work_list %}
16 | {% include 'spectator_events/includes/work.html' with work=work only %}
17 | {% endfor %}
18 |
19 | {% endif %}
20 |
--------------------------------------------------------------------------------
/src/spectator/events/templates/spectator_events/includes/works_paginated.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | For displaying a paginated list of works that are attached to an Event by a ManyToManyField. e.g., Classical Works or Dance Pieces.
3 |
4 | Expects:
5 | * selection_list - A list/QuerySet of ClassicalWorkSelections, MovieSelections, etc.
6 | * page_obj - A DiggPaginator instance.
7 | {% endcomment %}
8 |
9 | {% if work_list|length > 0 %}
10 | {% if page_obj|default:False and page_obj.number > 1 %}
11 | {% include 'spectator_core/includes/pagination.html' with page_obj=page_obj only %}
12 | {% endif %}
13 |
14 | {% include 'spectator_events/includes/works.html' with work_list=work_list heading=heading|default:None only %}
15 |
16 | {% include 'spectator_core/includes/pagination.html' with page_obj=page_obj only %}
17 |
18 | {% else %}
19 |
15 | {% if url_name == 'spectator:reading:publicationseries_list' %}
16 | Series
17 | {% else %}
18 | Series
19 | {% endif %}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/spectator/reading/templates/spectator_reading/includes/card_publications.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Used by the in_progress_publications_card template tag.
3 |
4 | Expects:
5 | * publication_list - A QuerySet of Publications.
6 | * card_title - The title for the card.
7 | {% endcomment %}
8 |
9 | {% if publication_list|length > 0 %}
10 |
11 |
12 |
{{ card_title }}
13 | {% include 'spectator_reading/includes/publications.html' with publication_list=publication_list show_readings='none' style='unstyled' only %}
14 |
15 |
16 | {% endif %}
17 |
--------------------------------------------------------------------------------
/src/spectator/reading/templates/spectator_reading/includes/card_years.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Used by the reading_years_card template tag.
3 |
4 | Expects:
5 |
6 | * current_year: A date object representing the current year, if any.
7 | * years: A QuerySet of date objects, one for each year to link to.
8 | {% endcomment %}
9 |
10 | {% if years|length > 0 %}
11 | {% load spectator_core %}
12 | {% current_url_name as url_name %}
13 |
54 | {% endif %}
55 |
56 | {% if show_thumbnail|default_if_none:False and publication.thumbnail %}
57 |
58 |
59 | {% endif %}
60 |
--------------------------------------------------------------------------------
/src/spectator/reading/templates/spectator_reading/includes/publications.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Expects:
3 |
4 | * publication_list: The list or QuerySet of Publications to display.
5 | * show_readings: 'none' (default), 'all' or 'current' (the in-progress reading, if any).
6 | * show_thumbnails: Boolean, default is False.
7 | * style: 'bullets' (default) or 'unstyled'.
8 |
9 | {% endcomment %}
10 |
11 |
12 | {% for publication in publication_list %}
13 |
14 | {% include 'spectator_reading/includes/publication.html' with publication=publication show_readings=show_readings|default:'none' show_thumbnail=show_thumbnails|default_if_none:False only %}
15 |
16 | {% endfor %}
17 |
18 |
--------------------------------------------------------------------------------
/src/spectator/reading/templates/spectator_reading/includes/publications_paginated.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Displays a list of publications, with pagination.
3 |
4 | Expects:
5 | * publication_list - List or Queryset of Publications.
6 | * show_readings: 'none' (default), 'all' or 'current' (the in-progress reading, if any).
7 | * page_obj, a DiggPaginator instance.
8 |
9 | {% endcomment %}
10 |
11 | {% if publication_list|length > 0 %}
12 | {% if page_obj|default:False and page_obj.number > 1 %}
13 | {% include 'spectator_core/includes/pagination.html' with page_obj=page_obj only %}
14 | {% endif %}
15 |
16 | {% include 'spectator_reading/includes/publications.html' with publication_list=publication_list show_readings=show_readings|default:'none' show_thumbnails=True only %}
17 |
18 | {% include 'spectator_core/includes/pagination.html' with page_obj=page_obj only %}
19 |
20 | {% else %}
21 |
22 |
36 | {% endif %}
37 |
38 | {% if reading_list|length > 0 %}
39 |
40 | {% if show_months|default_if_none:"True" is False %}
41 |
42 | {% endif %}
43 |
44 | {% for reading in reading_list %}
45 |
46 | {% if show_months|default_if_none:"True" is True %}
47 | {% ifchanged reading.end_date|date:"m" %}
48 | {% if not forloop.first %}
49 |
50 | {% endif %}
51 |
{{ reading.end_date|date:"F"}}
52 |
53 | {% endifchanged %}
54 | {% endif %}
55 |
56 | {% include 'spectator_reading/includes/publication.html' with publication=reading.publication show_readings='none' show_thumbnail=True only %}
57 |
58 | {% endfor %}
59 |
60 |
61 | {% include 'spectator_core/includes/pager.html' with url_name='spectator:reading:reading_year_archive' previous=previous_year next=next_year only %}
62 |
63 | {% else %}
64 |