Skip to content

Commit

Permalink
Create PronounSeries and Gender models; delete Pronoun model (#13)
Browse files Browse the repository at this point in the history
Aside from some extra refactoring and reformatting, the other [semi-major] change is moving the `DocumentManager` class to its own file, `managers.py`.
  • Loading branch information
joshfeli authored Jun 25, 2021
1 parent 0ce350c commit 59c1939
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 71 deletions.
3 changes: 2 additions & 1 deletion backend/app/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from . import models

models_to_register = [
models.Pronoun,
models.Document,
models.PronounSeries,
models.Gender,
]

for model in models_to_register:
Expand Down
11 changes: 11 additions & 0 deletions backend/app/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Custom managers for the gender analysis web app.
"""
from django.db import models


class DocumentManager(models.Manager):
def create_document(self, **attributes):
doc = self.create(**attributes)
doc.get_tokenized_text_wc_and_pos()
return doc
37 changes: 37 additions & 0 deletions backend/app/migrations/0003_pronounseries_gender_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 3.1.5 on 2021-06-25 17:10

import app.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('app', '0002_document'),
]

operations = [
migrations.CreateModel(
name='PronounSeries',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('identifier', models.CharField(max_length=60)),
('obj', app.fields.LowercaseCharField(max_length=40)),
('pos_det', app.fields.LowercaseCharField(max_length=40)),
('pos_pro', app.fields.LowercaseCharField(max_length=40)),
('reflex', app.fields.LowercaseCharField(max_length=40)),
('subj', app.fields.LowercaseCharField(max_length=40)),
],
),
migrations.CreateModel(
name='Gender',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('label', models.CharField(max_length=60)),
('pronoun_series', models.ManyToManyField(to='app.PronounSeries')),
],
),
migrations.DeleteModel(
name='Pronoun',
),
]
14 changes: 14 additions & 0 deletions backend/app/migrations/0005_merge_document_gender.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 3.1.5 on 2021-06-25 17:22

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('app', '0004_auto_20210623_1831'),
('app', '0003_pronounseries_gender_models'),
]

operations = [
]
266 changes: 245 additions & 21 deletions backend/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,264 @@
from more_itertools import windowed
from django.db import models
from .fields import LowercaseCharField
from .managers import DocumentManager


class Pronoun(models.Model):
class PronounSeries(models.Model):
"""
A model that allows users to define an individual pronoun and its type
(e.g. subject, object, reflexive, etc). Pronouns are case-insensitive and will be
converted to lowercase.
A class that allows users to define a custom series of pronouns to be used in
analysis functions
"""
PRONOUN_TYPES = [
('subj', 'Subject'),
('obj', 'Object'),
('pos_det', 'Possessive determiner'),
('pos_pro', 'Possessive pronoun'),
('reflex', 'Reflexive'),
]

identifier = LowercaseCharField(max_length=40)
type = models.CharField(max_length=7, choices=PRONOUN_TYPES)
# Things to consider:
# Add a default to reflex? i.e. default = object pronoun + 'self'?
# Also, how to we run doctests here? Or use pytest? (configs don't recognize django package or relative filepath
# in import statement)
identifier = models.CharField(max_length=60)
subj = LowercaseCharField(max_length=40)
obj = LowercaseCharField(max_length=40)
pos_det = LowercaseCharField(max_length=40)
pos_pro = LowercaseCharField(max_length=40)
reflex = LowercaseCharField(max_length=40)

@property
def all_pronouns(self):
"""
:return: The set of all pronoun identifiers.
"""
pronoun_set = {
self.subj,
self.obj,
self.pos_det,
self.pos_pro,
self.reflex,
}

return pronoun_set

def __contains__(self, pronoun):
"""
Checks to see if the given pronoun exists in this group. This check is case-insensitive
>>> pronouns = ['They', 'Them', 'Their', 'Theirs', 'Themself']
>>> pronoun_group = PronounSeries.objects.create('Andy', *pronouns)
>>> 'they' in pronoun_group
True
>>> 'hers' in pronoun_group
False
:param pronoun: The pronoun to check for in this group
:return: True if the pronoun is in the group, False otherwise
"""

return pronoun.lower() in self.all_pronouns

def __iter__(self):
"""
Allows the user to iterate over all of the pronouns in this group. Pronouns
are returned in lowercase and order is not guaranteed.
>>> pronouns = ['she', 'her', 'her', 'hers', 'herself']
>>> pronoun_group = PronounSeries.objects.create('Fem', *pronouns)
>>> sorted(pronoun_group)
['her', 'hers', 'herself', 'she']
"""

yield from self.all_pronouns

def __repr__(self):
"""
>>> PronounSeries.objects.create(
... identifier='Masc',
... subj='he',
... obj='him',
... pos_det='his',
... pos_pro='his',
... reflex='himself'
... )
<Masc: ['he', 'him', 'himself', 'his']>
:return: A console-friendly representation of the pronoun series
"""

return f'<{self.identifier}: {list(sorted(self))}>'

def __str__(self):
"""
>>> str(PronounSeries.objects.create('Andy', *['Xe', 'Xem', 'Xis', 'Xis', 'Xemself']))
'Andy-series'
:return: A string-representation of the pronoun series
"""

return self.identifier + '-series'

def __hash__(self):
"""
Makes the `PronounSeries` class hashable
"""

return self.identifier.__hash__()

def __eq__(self, other):
"""
Determines whether two `PronounSeries` are equal. Note that they are only equal if
they have the same identifier and the exact same set of pronouns.
>>> fem_series = PronounSeries.create(
... identifier='Fem',
... subj='she',
... obj='her',
... pos_det='her',
... pos_pro='hers',
... reflex='herself'
... )
>>> second_fem_series = PronounSeries.create(
... identifier='Fem',
... subj='she',
... obj='her',
... pos_pro='hers',
... reflex='herself'
... pos_det='HER',
... )
>>> fem_series == second_fem_series
True
>>> masc_series = PronounSeries.create(
... identifier='Masc',
... subj='he',
... obj='him',
... pos_det='his',
... pos_pro='his',
... reflex='himself'
... )
>>> fem_series == masc_series
False
:param other: The `PronounSeries` object to compare
:return: `True` if the two series are the same, `False` otherwise.
"""

return (
self.identifier == other.identifier
and sorted(self) == sorted(other)
)


class Gender(models.Model):
"""
This model defines a gender that analysis functions will use to operate.
"""

label = models.CharField(max_length=60)
pronoun_series = models.ManyToManyField(PronounSeries)

def __repr__(self):
return f'Pronoun({self.identifier, self.type})'
"""
:return: A console-friendly representation of the gender
>>> Gender('Female')
<Female>
"""

return f'<{self.label}>'

def __str__(self):
return f'Pronoun: {self.identifier}\nType: {self.get_type_display()}'
"""
:return: A string representation of the gender
>>> str(Gender('Female')
'Female'
"""

return self.label

def __hash__(self):
"""
Allows the Gender object to be hashed
"""

return self.label.__hash__()

def __eq__(self, other):
return self.identifier == other.identifier
"""
Performs a check to see whether two `Gender` objects are equivalent. This is true if and
only if the `Gender` identifiers, pronoun series, and names are identical.
Note that this comparison works:
>>> fem_pronouns = PronounSeries.objects.create('Fem', *['she', 'her', 'her', 'hers', 'herself'])
>>> female = Gender.objects.create('Female')
>>> female.pronoun_series.add(1)
>>> another_female = Gender.objects.create('Female')
>>> another_female.pronoun_series.add(1)
>>> female == another_female
True
But this one does not:
>>> they_series = PronounSeries.objects.create('They', *['they', 'them', 'their', 'theirs', 'themselves'])
>>> xe_series = PronounSeries.objects.create('They', *['Xe', 'Xem', 'Xis', 'Xis', 'Xemself'])
>>> androgynous_1 = Gender.objects.create('NB')
>>> androgynous_1.pronoun_series.add(2)
>>> androgynous_2 = Gender.objects.create('NB')
>>> androgynous_2.pronoun_series.add(3)
>>> androgynous_1 == androgynous_2
False
:param other: The other `Gender` object to compare
:return: `True` if the `Gender`s are the same, `False` otherwise
"""

return (
self.label == other.label
and list(self.pronoun_series.all()) == list(other.pronoun_series.all())
)

@property
def pronouns(self):
"""
:return: A set containing all pronouns that this `Gender` uses
>>> they_series = PronounSeries.objects.create('They', *['they', 'them', 'their', 'theirs', 'themselves'])
>>> xe_series = PronounSeries('Xe', *['Xe', 'Xer', 'Xis', 'Xis', 'Xerself'])
>>> androgynous = Gender.objects.create('Androgynous')
>>> androgynous.pronoun_series.add(1, 2)
>>> androgynous.pronouns == {'they', 'them', 'theirs', 'xe', 'xer', 'xis'}
True
"""

all_pronouns = set()
for series in list(self.pronoun_series.all()):
all_pronouns |= series.all_pronouns

return all_pronouns

@property
def subj(self):
"""
:return: set of all subject pronouns used to describe the gender
>>> fem_pronouns = PronounSeries('Fem', {'she', 'her', 'hers'}, subj='she', obj='her')
>>> masc_pronouns = PronounSeries('Masc', {'he', 'him', 'his'}, subj='he', obj='him')
>>> bigender = Gender('Bigender', [fem_pronouns, masc_pronouns])
>>> bigender.subj == {'he', 'she'}
True
"""

subject_pronouns = set()
for series in list(self.pronoun_series.all()):
subject_pronouns.add(series.subj)

return subject_pronouns

@property
def obj(self):
"""
:return: set of all object pronouns used to describe the gender
>>> fem_pronouns = PronounSeries('Fem', {'she', 'her', 'hers'}, subj='she', obj='her')
>>> masc_pronouns = PronounSeries('Masc', {'he', 'him', 'his'}, subj='he', obj='him')
>>> bigender = Gender('Bigender', [fem_pronouns, masc_pronouns])
>>> bigender.obj == {'him', 'her'}
True
"""

class DocumentManager(models.Manager):
def create_document(self, **attributes):
doc = self.create(**attributes)
doc.get_tokenized_text_wc_and_pos()
return doc
subject_pronouns = set()
for series in list(self.pronoun_series.all()):
subject_pronouns.add(series.obj)
return subject_pronouns


class Document(models.Model):
Expand Down
7 changes: 0 additions & 7 deletions backend/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,10 @@
# import json
from rest_framework import serializers
from .models import (
Pronoun,
Document,
)


class PronounSerializer(serializers.ModelSerializer):
class Meta:
model = Pronoun
fields = ['id', 'identifier', 'type']


class DocumentSerializer(serializers.ModelSerializer):
"""
Serializes a Document object
Expand Down
Loading

0 comments on commit 59c1939

Please sign in to comment.