Princeton-CDH / mep-django
Showing 18 of 29 files from the diff.

@@ -151,10 +151,10 @@
Loading
151 151
152 152
    def test_error_on_create_non_field(self):
153 153
        # get with no instance should return the descriptor object
154 -
        class TestModel(DateRange):
154 +
        class TestModel2(DateRange):
155 155
            foo_year = AliasIntegerField(db_column='start_year')
156 156
157 -
        assert isinstance(TestModel.foo_year, AliasIntegerField)
157 +
        assert isinstance(TestModel2.foo_year, AliasIntegerField)
158 158
159 159
160 160
class TestVerifyLatLon(TestCase):

@@ -238,7 +238,8 @@
Loading
238 238
        'id', 'name', 'sort_name', 'mep_id', 'account_id', 'birth_year',
239 239
        'death_year', 'gender', 'title', 'profession', 'is_organization',
240 240
        'is_creator', 'has_account', 'in_logbooks', 'has_card',
241 -
        'subscription_dates', 'verified', 'updated_at', 'admin_url'
241 +
        'subscription_dates', 'verified', 'updated_at', 'admin_url',
242 +
        'viaf_id'
242 243
    ]
243 244
244 245
    def csv_filename(self):

@@ -0,0 +1,14 @@
Loading
1 +
# Generated by Django 2.2.11 on 2020-12-10 17:40
2 +
3 +
from django.db import migrations
4 +
5 +
6 +
class Migration(migrations.Migration):
7 +
8 +
    dependencies = [
9 +
        ('accounts', '0034_subscription_other_to_separate_deposit'),
10 +
        ('accounts', '0034_add_event_footnotes'),
11 +
    ]
12 +
13 +
    operations = [
14 +
    ]

@@ -1,5 +1,6 @@
Loading
1 1
import datetime
2 2
import logging
3 +
from string import punctuation
3 4
4 5
from django.apps import apps
5 6
from django.contrib.contenttypes.fields import GenericRelation
@@ -670,7 +671,7 @@
Loading
670 671
            # text version of sort name for search and display
671 672
            'sort_name_t': self.sort_name,
672 673
            # sort version of sort name
673 -
            'sort_name_isort': self.sort_name,
674 +
            'sort_name_isort': self.sort_name.lstrip(punctuation),
674 675
            'birth_year_i': self.birth_year,
675 676
            'death_year_i': self.death_year,
676 677
            'has_card_b': self.has_card(),

@@ -115,7 +115,7 @@
Loading
115 115
    # can only be child of Root
116 116
    parent_page_types = [Page]
117 117
    # only landingpage subtypes as children
118 -
    subpage_types = ['ContentLandingPage', 'EssayLandingPage']
118 +
    subpage_types = ['ContentLandingPage', 'EssayLandingPage', 'ContentPage']
119 119
    #: main page text
120 120
    body = StreamField(BodyContentBlock)
121 121
@@ -420,8 +420,8 @@
Loading
420 420
    '''A simple :class:`BasePage` type that appears beneath `ContentLandingPage`s
421 421
    in the hierarchy.'''
422 422
423 -
    # can only be child of ContentLandingPage
424 -
    parent_page_types = ['ContentLandingPage']
423 +
    # can be child of ContentLandingPage or HomePage
424 +
    parent_page_types = ['ContentLandingPage', 'HomePage']
425 425
    # no allowed children
426 426
    subpage_types = []
427 427
@@ -435,7 +435,6 @@
Loading
435 435
    # no allowed children
436 436
    subpage_types = []
437 437
438 -
439 438
    # taken from PPA
440 439
    def set_url_path(self, parent):
441 440
        """

@@ -44,7 +44,7 @@
Loading
44 44
        'item_uri', 'item_title', 'item_volume', 'item_authors',
45 45
        'item_year', 'item_notes',
46 46
        # footnote/citation
47 -
        'source_citation', 'source_manifest', 'source_image'
47 +
        'source_type', 'source_citation', 'source_manifest', 'source_image'
48 48
    ]
49 49
50 50
    def get_queryset(self):
@@ -172,6 +172,7 @@
Loading
172 172
    def source_info(self, footnote):
173 173
        '''source details from a footnote'''
174 174
        source_info = OrderedDict([
175 +
            ('type', footnote.bibliography.source_type.name),
175 176
            ('citation', footnote.bibliography.bibliographic_note)
176 177
        ])
177 178
        if footnote.bibliography.manifest:

@@ -1,5 +1,7 @@
Loading
1 1
import datetime
2 2
3 +
from django.conf import settings
4 +
from django.contrib.admin.models import ADDITION
3 5
from django.db import connection
4 6
from django.db.migrations.executor import MigrationExecutor
5 7
from django.test import TransactionTestCase
@@ -323,3 +325,103 @@
Loading
323 325
        assert account4_renew2.purchase_date == self.account4_renew2.start_date
324 326
        assert account4_renew2.start_date == account4_renew.end_date
325 327
        assert account4_renew2.end_date == datetime.date(1920, 8, 1)
328 +
329 +
330 +
@pytest.mark.last
331 +
class TestEventAddFootnotes(TestMigrations):
332 +
333 +
    app = 'accounts'
334 +
    migrate_from = '0033_subscription_purchase_date_adjustments'
335 +
    migrate_to = '0034_add_event_footnotes'
336 +
337 +
    def setUpBeforeMigration(self, apps):
338 +
339 +
        Account = apps.get_model('accounts', 'Account')
340 +
        Subscription = apps.get_model('accounts', 'Subscription')
341 +
        SourceType = apps.get_model('footnotes', 'SourceType')
342 +
        Bibliography = apps.get_model('footnotes', 'Bibliography')
343 +
        Footnote = apps.get_model('footnotes', 'Footnote')
344 +
        ContentType = apps.get_model('contenttypes', 'ContentType')
345 +
        User = apps.get_model('auth', 'User')
346 +
347 +
        event_content_type = ContentType.objects \
348 +
            .get(model='event', app_label='accounts')
349 +
350 +
        # create script user
351 +
        User.objects.get_or_create(username=settings.SCRIPT_USERNAME)
352 +
353 +
        # create bibliography & source entries for migration
354 +
        addressbook_source = SourceType.objects \
355 +
            .get_or_create(name='Address Book')[0]
356 +
        logbook_source = SourceType.objects \
357 +
            .get_or_create(name='Logbook')[0]
358 +
        self.addressbook_1936 = Bibliography.objects.create(
359 +
            bibliographic_note="Address Book 1919–1935",
360 +
            source_type=addressbook_source)
361 +
        self.addressbook_post1936 = Bibliography.objects.create(
362 +
            bibliographic_note="Address Book 1935–1937",
363 +
            source_type=addressbook_source)
364 +
        self.logbooks = Bibliography.objects.create(
365 +
            bibliographic_note="Logbooks 1919–1941",
366 +
            source_type=logbook_source)
367 +
368 +
        # create Account to hold events
369 +
        account = Account.objects.create()
370 +
371 +
        # create sub with footnote
372 +
        self.sub_with_note = Subscription.objects.create(
373 +
            account=account,
374 +
            start_date=datetime.date(1950, 1, 5))
375 +
        Footnote.objects.create(
376 +
            bibliography=self.addressbook_1936, is_agree=True,
377 +
            content_type=event_content_type, object_id=self.sub_with_note.pk)
378 +
        # subs with P36ADD and 36ADD tags
379 +
        self.sub_p36add_with_note = Subscription.objects.create(
380 +
            account=account, notes='P36ADD',
381 +
            start_date=datetime.date(1950, 2, 5))
382 +
        Footnote.objects.create(
383 +
            bibliography=self.addressbook_post1936, is_agree=True,
384 +
            content_type=event_content_type, object_id=self.sub_p36add_with_note.pk)
385 +
        self.sub_p36add = Subscription.objects.create(
386 +
            account=account, notes='P36ADD',
387 +
            start_date=datetime.date(1950, 2, 15))
388 +
        self.sub_36add = Subscription.objects.create(
389 +
            account=account, notes='36ADD',
390 +
            start_date=datetime.date(1950, 3, 5))
391 +
        # sub with no tags or footnotes
392 +
        self.sub_logbook = Subscription.objects.create(
393 +
            account=account,
394 +
            start_date=datetime.date(1950, 2, 5))
395 +
396 +
    def test_added_footnotes(self):
397 +
        Footnote = self.apps.get_model('footnotes', 'Footnote')
398 +
        ContentType = self.apps.get_model('contenttypes', 'ContentType')
399 +
        LogEntry = self.apps.get_model('admin', 'LogEntry')
400 +
401 +
        event_content_type = ContentType.objects \
402 +
            .get(model='event', app_label='accounts')
403 +
        footnote_content_type = ContentType.objects \
404 +
            .get(model='footnote', app_label='footnotes')
405 +
406 +
        # pre-filtered querysets for the following tests
407 +
        event_footnotes = Footnote.objects.filter(content_type=event_content_type)
408 +
        log_entries = LogEntry.objects.filter(
409 +
            content_type_id=footnote_content_type, action_flag=ADDITION)
410 +
411 +
        # should not add a second footnote
412 +
        assert event_footnotes.filter(object_id=self.sub_with_note.pk) \
413 +
            .count() == 1
414 +
        # no second footnote even if there is a tag
415 +
        assert event_footnotes.filter(object_id=self.sub_p36add_with_note.pk) \
416 +
            .count() == 1
417 +
418 +
        # new footnotes created with correct sources and log entries
419 +
        sub_p36add_fn = event_footnotes.get(object_id=self.sub_p36add.pk)
420 +
        assert sub_p36add_fn.bibliography.pk == self.addressbook_post1936.pk
421 +
        assert log_entries.filter(object_id=sub_p36add_fn.pk).count() == 1
422 +
        sub_36add_fn = event_footnotes.get(object_id=self.sub_36add.pk)
423 +
        assert sub_36add_fn.bibliography.pk == self.addressbook_1936.pk
424 +
        assert log_entries.filter(object_id=sub_36add_fn.pk).count() == 1
425 +
        logbook_fn = event_footnotes.get(object_id=self.sub_logbook.pk)
426 +
        assert logbook_fn.bibliography.pk == self.logbooks.pk
427 +
        assert log_entries.filter(object_id=logbook_fn.pk).count() == 1

@@ -1,4 +1,4 @@
Loading
1 -
__version_info__ = (1, 2, 4, None)
1 +
__version_info__ = (1, 3, 0, None)
2 2
3 3
4 4
# Dot-connect all but the last. Last is dash-connected if not None.

@@ -1,5 +1,6 @@
Loading
1 1
# Generated by Django 2.2.11 on 2020-05-04 18:09
2 2
3 +
from django.contrib.auth.management import create_permissions
3 4
from django.db import migrations
4 5
5 6
@@ -8,12 +9,19 @@
Loading
8 9
    ContentType = apps.get_model('contenttypes', 'ContentType')
9 10
    Event = apps.get_model('accounts', 'Event')
10 11
12 +
    # run create permissions for all accounts apps to ensure
13 +
    # that needed content types are created
14 +
    accounts_app_config = apps.get_app_config('accounts')
15 +
    accounts_app_config.models_module = True
16 +
    create_permissions(accounts_app_config, apps=apps, verbosity=0)
17 +
    accounts_app_config.models_module = None
18 +
11 19
    # get content types for the event models
12 -
    event_ctype = ContentType.objects.get(app_label='accounts', model='Event')
20 +
    event_ctype = ContentType.objects.get(app_label='accounts', model='event')
13 21
    borrow_ctype = ContentType.objects.get(app_label='accounts',
14 -
                                           model='Borrow')
22 +
                                           model='borrow')
15 23
    purchase_ctype = ContentType.objects.get(app_label='accounts',
16 -
                                             model='Purchase')
24 +
                                             model='purchase')
17 25
18 26
    # update all footnotes linked to borrows to event content type
19 27
    # and event id for associated borrow
@@ -35,6 +43,8 @@
Loading
35 43
36 44
    dependencies = [
37 45
        ('footnotes', '0004_on_delete'),
46 +
        ('accounts', '0031_on_delete'),
47 +
        ('contenttypes', '0002_remove_content_type_name')
38 48
    ]
39 49
40 50
    operations = [

@@ -0,0 +1,119 @@
Loading
1 +
# Generated by Django 2.2.11 on 2020-12-08 14:53
2 +
3 +
from django.db import migrations
4 +
from django.contrib.admin.models import ADDITION
5 +
from django.conf import settings
6 +
7 +
8 +
def add_missing_footnotes(apps, schema_editor):
9 +
    '''
10 +
    Add footnotes to document source for events without footnotes.
11 +
    Events should be associated with one of two address books based on
12 +
    P36ADD and 36ADD tags in the event notes; all other events without
13 +
    footnotes are from the logbooks.
14 +
    '''
15 +
    Bibliography = apps.get_model('footnotes', 'Bibliography')
16 +
    Footnote = apps.get_model('footnotes', 'Footnote')
17 +
    Event = apps.get_model('accounts', 'Event')
18 +
    ContentType = apps.get_model('contenttypes', 'ContentType')
19 +
    User = apps.get_model('auth', 'User')
20 +
    LogEntry = apps.get_model('admin', 'LogEntry')
21 +
22 +
    event_content_type = ContentType.objects \
23 +
        .get(model='event', app_label='accounts')
24 +
    footnote_content_type = ContentType.objects \
25 +
        .get(model='footnote', app_label='footnotes')
26 +
    script_user = User.objects.get(username=settings.SCRIPT_USERNAME)
27 +
28 +
    # get bibliographic entries to be used as sources for footnotes
29 +
    # (but handle case where they don't exist, in a new db)
30 +
    addressbook_1936 = Bibliography.objects \
31 +
        .filter(bibliographic_note__contains="Address Book 1919–1935",
32 +
                source_type__name='Address Book').first()
33 +
    addressbook_post1936 = Bibliography.objects \
34 +
        .filter(bibliographic_note__contains="Address Book 1935–1937",
35 +
                source_type__name='Address Book').first()
36 +
    logbooks = Bibliography.objects \
37 +
        .filter(bibliographic_note__contains="Logbooks 1919–1941",
38 +
                source_type__name='Logbook').first()
39 +
40 +
    # because footnote is a generic relation, it can't be used in a
41 +
    # queryset filter in a migration; instead, get a list event ids
42 +
    # with footnotes, so we can exclude them
43 +
    events_with_footnotes = Footnote.objects \
44 +
        .filter(content_type=event_content_type) \
45 +
        .values_list('object_id')
46 +
47 +
    if addressbook_post1936:
48 +
        # find all events with tag P36ADD and no footnote
49 +
        events_post36add = Event.objects.filter(notes__contains='P36ADD') \
50 +
                                        .exclude(pk__in=events_with_footnotes)
51 +
        # for each event: create footnote, then create log entry for the footnote
52 +
        # NOTE: not using bulk create because the objects it returns don't have pks
53 +
        for event in events_post36add:
54 +
            footnote = Footnote.objects.create(
55 +
                bibliography=addressbook_post1936,
56 +
                content_type=event_content_type,
57 +
                object_id=event.pk)
58 +
            LogEntry.objects.create(
59 +
                user_id=script_user.id,
60 +
                content_type_id=footnote_content_type.pk,
61 +
                object_id=footnote.pk,
62 +
                object_repr='Footnote on event %s for %s' %
63 +
                            (event.pk, addressbook_post1936.bibliographic_note),
64 +
                change_message='Address book footnote created based on P36ADD tag',
65 +
                action_flag=ADDITION)
66 +
67 +
    if addressbook_1936:
68 +
        # find events from the 1936 address book; exclude events from
69 +
        # post-1936 address book or tha talready have
70 +
        events_36add = Event.objects.filter(notes__contains='36ADD') \
71 +
                            .exclude(notes__contains='P36ADD') \
72 +
                            .exclude(pk__in=events_with_footnotes)
73 +
        # for each event: create footnote, then create log entry for the footnote
74 +
        for event in events_36add:
75 +
            footnote = Footnote.objects.create(
76 +
                bibliography=addressbook_1936,
77 +
                content_type=event_content_type,
78 +
                object_id=event.pk)
79 +
            LogEntry.objects.create(
80 +
                user_id=script_user.id,
81 +
                content_type_id=footnote_content_type.pk,
82 +
                object_id=footnote.pk,
83 +
                object_repr='Footnote on event %s for %s' %
84 +
                            (event.pk, addressbook_1936.bibliographic_note),
85 +
                change_message='Address book footnote created based on 36ADD tag',
86 +
                action_flag=ADDITION)
87 +
88 +
    if logbooks:
89 +
        # find all remaining events without footnotes — from the logbooks
90 +
        logbooks_events = Event.objects.exclude(notes__contains='36ADD') \
91 +
                               .exclude(pk__in=events_with_footnotes)
92 +
        for event in logbooks_events:
93 +
            footnote = Footnote.objects.create(
94 +
                bibliography=logbooks,
95 +
                content_type=event_content_type,
96 +
                object_id=event.pk)
97 +
            LogEntry.objects.create(
98 +
                user_id=script_user.id,
99 +
                content_type_id=footnote_content_type.pk,
100 +
                object_id=footnote.pk,
101 +
                object_repr='Footnote on event %s for %s' %
102 +
                            (event.pk, logbooks.bibliographic_note),
103 +
                change_message='Associated with logbooks',
104 +
                action_flag=ADDITION)
105 +
106 +
107 +
class Migration(migrations.Migration):
108 +
109 +
    dependencies = [
110 +
        ('accounts', '0033_subscription_purchase_date_adjustments'),
111 +
        ('footnotes', '0005_consolidate_event_footnotes'),
112 +
        ('admin', '0003_logentry_add_action_flag_choices'),
113 +
        ('common', '0005_create_script_user')
114 +
    ]
115 +
116 +
    operations = [
117 +
        migrations.RunPython(add_missing_footnotes,
118 +
                             migrations.RunPython.noop)
119 +
    ]

@@ -268,6 +268,14 @@
Loading
268 268
        assert twitterbot_100years.work_label(the_dial) == \
269 269
            "an issue of “The Dial.”"
270 270
271 +
    def test_redaction(self):
272 +
        tenlittle = Work(title='Ten Little Niggers')
273 +
        assert twitterbot_100years.work_label(tenlittle) == \
274 +
            "“Ten Little N[-----]s.”"
275 +
        tenlittle.title = 'Nigger Heaven'
276 +
        assert twitterbot_100years.work_label(tenlittle) == \
277 +
            "“N[-----] Heaven.”"
278 +
271 279
272 280
class TestCanTweet(TestCase):
273 281
    fixtures = ['test_events']
@@ -363,6 +371,8 @@
Loading
363 371
            (borrow.account.persons.first().name,
364 372
             twitterbot_100years.work_label(borrow.work))
365 373
        assert borrowed in tweet
374 +
        # books with no date should not have a duplicate period
375 +
        assert '.”.' not in tweet
366 376
367 377
        # return
368 378
        tweet = tweet_content(borrow, borrow.partial_end_date)
@@ -380,6 +390,16 @@
Loading
380 390
        assert 'subscribed for 1 month, 2 volumes at a time'  \
381 391
            in tweet
382 392
393 +
    def test_request_event(self):
394 +
        # find a borrow with fully known start and end date
395 +
        borrow = Event.objects.filter(
396 +
            borrow__isnull=False, start_date__isnull=False,
397 +
            end_date__isnull=False, start_date_precision=7).first()
398 +
        # add notation to make it a request
399 +
        borrow.notes = 'NOTATION: REQUEST'
400 +
        tweet = tweet_content(borrow, borrow.partial_start_date)
401 +
        assert 'requested' in tweet
402 +
383 403
    def test_tweet_text_tag(self):
384 404
        subs = Event.objects.get(pk=8810)
385 405
        subs.subscription.purchase_date = subs.start_date

@@ -527,6 +527,7 @@
Loading
527 527
        assert info['citation'] == footnote.bibliography.bibliographic_note
528 528
        assert info['manifest'] == footnote.bibliography.manifest.uri
529 529
        assert info['image'] == str(footnote.image.image)
530 +
        assert info['type'] == footnote.bibliography.source_type.name
530 531
531 532
    def test_get_object_data(self):
532 533
        # get a subscription with no subcategory and both dates

@@ -436,6 +436,15 @@
Loading
436 436
        assert uk.name in index_data['nationality']
437 437
        assert denmark.name in index_data['nationality']
438 438
439 +
        # ensure that punctuation is stripped during sort
440 +
        pers = Person.objects.create(
441 +
            name='"Friend of John Smith"', birth_year=1855, death_year=1876,
442 +
            sort_name='"Friend of John Smith"'
443 +
        )
444 +
        acct = Account.objects.create()
445 +
        acct.persons.add(pers)
446 +
        index_data = pers.index_data()
447 +
        assert index_data['sort_name_isort'] == 'Friend of John Smith"'
439 448
440 449
class TestPersonQuerySet(TestCase):
441 450

@@ -695,11 +695,26 @@
Loading
695 695
        # creating new subscription for same account & date should error
696 696
        with pytest.raises(ValidationError):
697 697
            subscr = Subscription(account=self.account,
698 -
                start_date=self.subscription.start_date)
698 +
                                  start_date=self.subscription.start_date)
699 699
            subscr.validate_unique()
700 700
701 -
        # same account + date with different subtype should be fine
701 +
        # creating new subscription for same account with same
702 +
        # start date AND end date should error
703 +
        with pytest.raises(ValidationError):
704 +
            subscr = Subscription(account=self.account,
705 +
                                  start_date=self.subscription.start_date,
706 +
                                  end_date=self.subscription.end_date)
707 +
            subscr.validate_unique()
708 +
709 +
        # same account, type, start date but different end date is valid
702 710
        subscr = Subscription(account=self.account,
711 +
                              start_date=self.subscription.start_date,
712 +
                              end_date=datetime.date(1931, 3, 6))
713 +
        subscr.validate_unique()
714 +
715 +
        # same account + date with different subtype should be fine
716 +
        subscr = Subscription(
717 +
            account=self.account,
703 718
            start_date=self.subscription.start_date, subtype='ren')
704 719
        subscr.validate_unique()
705 720

@@ -113,7 +113,8 @@
Loading
113 113
        self.assertAllowedParentPageTypes(HomePage, [Page])
114 114
115 115
    def test_subpages(self):
116 -
        self.assertAllowedSubpageTypes(HomePage, [ContentLandingPage, EssayLandingPage])
116 +
        self.assertAllowedSubpageTypes(
117 +
            HomePage, [ContentLandingPage, EssayLandingPage, ContentPage])
117 118
118 119
    def test_template(self):
119 120
        site = Site.objects.first()
@@ -303,7 +304,8 @@
Loading
303 304
        self.assertTemplateUsed(response, 'pages/content_page.html')
304 305
305 306
    def test_parent_pages(self):
306 -
        self.assertAllowedParentPageTypes(ContentPage, [ContentLandingPage])
307 +
        self.assertAllowedParentPageTypes(ContentPage,
308 +
                                          [ContentLandingPage, HomePage])
307 309
308 310
    def test_subpages(self):
309 311
        self.assertAllowedSubpageTypes(ContentPage, [])
@@ -318,7 +320,8 @@
Loading
318 320
            'title': 'A new analysis esssay',
319 321
            'slug': 'new-essay',
320 322
            'body': streamfield([
321 -
                ('paragraph', rich_text('this page lives right under analysis'))
323 +
                ('paragraph',
324 +
                 rich_text('this page lives right under analysis'))
322 325
            ]),
323 326
            'authors-count': 0,
324 327
            'editors-count': 0

@@ -94,8 +94,11 @@
Loading
94 94
                self.stdout.write('\n')
95 95
96 96
    # times:  9 AM, 12 PM, 1:30 PM, 3 PM, 4:30 PM, 6 PM, 8 PM
97 -
    tweet_times = ['9:00', '12:00', '13:30', '15:00', '16:30', '18:00',
98 -
                   '20:00', '10:15', '11:30', '19:00']
97 +
    tweet_times = [
98 +
        '9:00', '12:00', '13:30', '15:00', '16:30',
99 +
        '18:00', '20:00', '10:15', '11:30', '19:00',
100 +
        '9:45', '12:45', '11:15', '14:15', '17:45',
101 +
    ]
99 102
100 103
    def schedule(self, date):
101 104
        '''Schedule all tweetable events for the specified date.'''
@@ -218,8 +221,8 @@
Loading
218 221
219 222
    elif event_label in ['Borrow', 'Purchase', 'Request']:
220 223
        tweet_pattern = 'verbed'
221 -
        # convert event type into verb for the sentence
222 -
        verb = '%sed' % ev.event_type.lower().rstrip('e')
224 +
        # convert event label into verb for the sentence
225 +
        verb = '%sed' % ev.event_label.lower().rstrip('e')
223 226
        if event_label == 'Borrow' and ev.end_date == day:
224 227
            verb = 'returned'
225 228
        work_text = work_label(ev.work)
@@ -227,7 +230,7 @@
Loading
227 230
            'verb': verb,
228 231
            'work': work_text,
229 232
            # don't duplicate period inside quotes when no year
230 -
            'period': '' if work_text[-1] == '.' else '.'
233 +
            'period': '' if work_text[-2] == '.' else '.'
231 234
        })
232 235
233 236
    elif event_label == 'Reimbursement':
@@ -288,8 +291,11 @@
Loading
288 291
    elif not work.year or work.year < 1500:
289 292
        title_punctuation = '.'
290 293
291 -
    parts.append('“%s%s”' % (work.title.strip('"“”'),
292 -
                 title_punctuation))
294 +
    # remove any straight or smart quotes included
295 +
    # redact offensive term known to occur in titles
296 +
    title = work.title.strip('"“”').replace('Nigger', 'N[-----]')
297 +
298 +
    parts.append('“%s%s”' % (title, title_punctuation))
293 299
294 300
    # add editors after title
295 301
    if include_editors:

@@ -0,0 +1,18 @@
Loading
1 +
# Generated by Django 2.2.11 on 2020-12-10 15:14
2 +
3 +
from django.db import migrations, models
4 +
5 +
6 +
class Migration(migrations.Migration):
7 +
8 +
    dependencies = [
9 +
        ('accounts', '0033_subscription_purchase_date_adjustments'),
10 +
    ]
11 +
12 +
    operations = [
13 +
        migrations.AlterField(
14 +
            model_name='subscription',
15 +
            name='subtype',
16 +
            field=models.CharField(blank=True, choices=[('', 'Subscription'), ('sup', 'Supplement'), ('ren', 'Renewal'), ('oth', 'Separate Deposit')], help_text='Type of subscription event, e.g. supplement or renewal.', max_length=50, verbose_name='Type'),
17 +
        ),
18 +
    ]

@@ -423,13 +423,13 @@
Loading
423 423
424 424
    SUPPLEMENT = 'sup'
425 425
    RENEWAL = 'ren'
426 -
    OTHER = 'oth'
426 +
    SEPARATE_DEPOSIT = 'oth'  # for historical reasons, this code is "other"
427 427
428 428
    EVENT_TYPE_CHOICES = (
429 429
        ('', 'Subscription'),
430 430
        (SUPPLEMENT, 'Supplement'),
431 431
        (RENEWAL, 'Renewal'),
432 -
        (OTHER, 'Other'),
432 +
        (SEPARATE_DEPOSIT, 'Separate Deposit'),
433 433
    )
434 434
    subtype = models.CharField(
435 435
        verbose_name='Type', max_length=50, blank=True,
@@ -464,7 +464,8 @@
Loading
464 464
        # (can't use unique_together because of multi-table inheritance)
465 465
466 466
        # adapted from https://stackoverflow.com/questions/7366363/adding-custom-django-model-validation
467 -
        qs = Subscription.objects.filter(start_date=self.start_date,
467 +
        qs = Subscription.objects.filter(
468 +
            start_date=self.start_date, end_date=self.end_date,
468 469
            account=self.account, subtype=self.subtype)
469 470
470 471
        # if current work is already saved, exclude it from the queryset
Files Coverage
mep 98.43%
srcmedia/ts 86.55%
Project Totals (226 files) 98.00%
2545.1
TRAVIS_PYTHON_VERSION=3.5
TRAVIS_OS_NAME=linux
2544.1
TRAVIS_PYTHON_VERSION=3.5
TRAVIS_OS_NAME=linux

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading