Princeton-CDH / mep-django

@@ -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 +
    ]

@@ -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,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,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 +
    ]
Files Coverage
mep 98.44%
srcmedia/ts 86.55%
Project Totals (226 files) 98.01%
2541.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