Skip to content

Commit

Permalink
Merge pull request #768 from fecgov/push-sprint-38
Browse files Browse the repository at this point in the history
Update requirements for sprint 38
  • Loading branch information
mjtravers authored Mar 11, 2024
2 parents 72ba029 + e225b5d commit 8b96871
Show file tree
Hide file tree
Showing 52 changed files with 902 additions and 670 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
docker:
- image: cimg/python:3.10-node
- image: cimg/postgres:12.8
- image: cimg/redis:6.2.6

steps:
- run:
Expand Down
3 changes: 2 additions & 1 deletion .safety.dependency.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
# Example:
# 40104 2022-01-15
#
63687 2024-04-01 # gitpython <3.1.41
63687 2024-05-01 # gitpython <3.1.41
64227 2024-05-01 # jinja2 <3.1.3
64976 2024-05-01 # django < 4.2.10
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ RUN useradd nxgu --no-create-home --home /opt/nxg_fec && chown -R nxgu:nxgu /opt
USER nxgu

EXPOSE 8080
ENTRYPOINT ["/bin/sh", "-c", "python wait_for_db.py && python manage.py migrate && python manage.py loaddata fixtures/e2e-test-data.json && gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 10 --reload"]
ENTRYPOINT ["/bin/sh", "-c", "python wait_for_db.py && python manage.py migrate && python manage.py loaddata fixtures/e2e-test-data.json && gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9 --reload"]
2 changes: 1 addition & 1 deletion Dockerfile-e2e
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ RUN useradd nxgu --no-create-home --home /opt/nxg_fec_e2e && chown -R nxgu:nxgu
USER nxgu

EXPOSE 8080
ENTRYPOINT ["/bin/sh", "-c", "python wait_for_db.py && python manage.py migrate && python manage.py loaddata fixtures/e2e-test-data.json && gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 10 --reload"]
ENTRYPOINT ["/bin/sh", "-c", "python wait_for_db.py && python manage.py migrate && python manage.py loaddata fixtures/e2e-test-data.json && gunicorn --bind 0.0.0.0:8080 fecfiler.wsgi -w 9 --reload"]
1 change: 0 additions & 1 deletion django-backend/data.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Generated by Django 4.2.7 on 2024-02-16 20:43

from django.conf import settings
from django.db import migrations, models
from django.utils import timezone
import uuid


def delete_pending_memberships(apps, schema_editor):
Membership = apps.get_model("committee_accounts", "Membership") # noqa
Membership.objects.filter(user=None).delete()


def generate_new_uuid(apps, schema_editor):
Membership = apps.get_model("committee_accounts", "Membership") # noqa
for membership in Membership.objects.all():
membership.uuid = uuid.uuid4()
membership.save()


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('committee_accounts', '0002_membership'),
]

operations = [
migrations.AddField(
model_name='membership',
name='pending_email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
migrations.AddField(
model_name='membership',
name='uuid',
field=models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=False,
serialize=False,
unique=False
)
),
migrations.RunPython(
generate_new_uuid,
migrations.RunPython.noop,
),
migrations.RemoveField(
model_name='membership',
name='id',
),
migrations.RenameField(
model_name='membership',
old_name='uuid',
new_name='id'
),
migrations.AlterField(
model_name='membership',
name='id',
field=models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True
)
),
migrations.AlterField(
model_name='membership',
name='user',
field=models.ForeignKey(
null=True,
on_delete=models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL
),
),
migrations.AddField(
model_name='membership',
name='created',
field=models.DateTimeField(auto_now_add=True, default=timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='membership',
name='updated',
field=models.DateTimeField(auto_now=True),
),
migrations.RunPython(
migrations.RunPython.noop,
delete_pending_memberships
),
]
12 changes: 11 additions & 1 deletion django-backend/fecfiler/committee_accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,21 @@ class CommitteeRole(models.TextChoices):
COMMITTEE_ADMINISTRATOR = "COMMITTEE_ADMINISTRATOR"
REVIEWER = "REVIEWER"

id = models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
)
committee_account = models.ForeignKey(CommitteeAccount, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
user = models.ForeignKey(User, null=True, blank=False, on_delete=models.CASCADE)
pending_email = models.EmailField(null=True, blank=True)
role = models.CharField(
max_length=25, choices=CommitteeRole.choices, null=False, blank=False
)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)


class CommitteeOwnedModel(models.Model):
Expand Down
53 changes: 41 additions & 12 deletions django-backend/fecfiler/committee_accounts/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from fecfiler.committee_accounts.models import CommitteeAccount
from fecfiler.committee_accounts.models import CommitteeAccount, Membership
from django.contrib.sessions.exceptions import SuspiciousSession
from rest_framework import serializers, relations
import structlog
Expand All @@ -12,18 +12,47 @@ class Meta:
fields = "__all__"


class CommitteeMemberSerializer(serializers.Serializer):
id = serializers.CharField()
email = serializers.CharField()
username = serializers.CharField()
first_name = serializers.CharField()
last_name = serializers.CharField()
role = serializers.SerializerMethodField()
is_active = serializers.BooleanField()
class CommitteeMembershipSerializer(serializers.Serializer):
role = serializers.ChoiceField(choices=Membership.CommitteeRole)

def get_role(self, object):
committee_id = self.context.get("committee_id")
return object.membership_set.get(committee_account_id=committee_id).role
def to_representation(self, instance):
representation = super().to_representation(instance)

if instance.user is not None:
representation.update({
'id': instance.id,
'email': instance.user.email,
'username': instance.user.username,
'name': f"{instance.user.last_name}, {instance.user.first_name}",
})
else:
representation.update({
'id': instance.id,
'email': instance.pending_email,
'username': '',
'name': '',
})

representation['is_active'] = instance.user is not None
return representation

class Meta:
model = Membership

fields = [
f.name
for f in Membership._meta.get_fields()
if f.name
not in [
"deleted",
"user",
"pending_email"
]
] + ["name", "email"]
read_only_fields = [
"id",
"created",
]


class CommitteeOwnedSerializer(serializers.ModelSerializer):
Expand Down
151 changes: 151 additions & 0 deletions django-backend/fecfiler/committee_accounts/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
from uuid import UUID
from django.test import RequestFactory, TestCase
from fecfiler.committee_accounts.models import Membership
from fecfiler.committee_accounts.views import register_committee, \
CommitteeMembershipViewSet
from fecfiler.user.models import User
from django.core.management import call_command


class CommitteeAccountsViewsTest(TestCase):

def setUp(self):
call_command("load_committee_data")
self.test_user = User.objects.create(
email="[email protected]", username="gov")
self.other_user = User.objects.create(
email="[email protected]", username="com")

def test_register_committee(self):
account = register_committee("C12345678", self.test_user)
self.assertEquals(account.committee_id, "C12345678")

def test_register_committee_existing(self):
account = register_committee("C12345678", self.test_user)
self.assertEquals(account.committee_id, "C12345678")
self.assertRaises(
Exception, register_committee, committee_id="C12345678", user=self.test_user
)

def test_register_committee_mismatch_email(self):
self.assertRaises(
Exception,
register_committee,
committee_id="C12345678",
user=self.other_user,
)


class CommitteeMemberViewSetTest(TestCase):
fixtures = ["C01234567_user_and_committee", "unaffiliated_users"]

def setUp(self):
self.user = User.objects.get(id="12345678-aaaa-bbbb-cccc-111122223333")
self.factory = RequestFactory()

def test_remove_member(self):
membership_uuid = UUID("136a21f2-66fe-4d56-89e9-0d1d4612741c")
view = CommitteeMembershipViewSet()
request = self.factory.get(
"/api/v1/committee-members/{membership_uuid}/remove-member"
)
request.user = self.user
request.session = {
"committee_uuid": UUID('11111111-2222-3333-4444-555555555555')}
request.method = "DELETE"
request.query_params = dict()
view.kwargs = {"pk": membership_uuid}
view.request = request
response = view.remove_member(
request, membership_uuid)
self.assertEqual(response.status_code, 200)

def test_add_pending_membership(self):
view = CommitteeMembershipViewSet()
request = self.factory.get("/api/v1/committee-members/add-member")
request.user = self.user
request.session = {
"committee_uuid": UUID('11111111-2222-3333-4444-555555555555')
}
request.method = "POST"
request.data = {
"role": Membership.CommitteeRole.COMMITTEE_ADMINISTRATOR,
"email": "[email protected]"
}
view.request = request
response = view.add_member(request)

self.assertEqual(response.status_code, 200)
self.assertEqual(
response.data['email'],
'[email protected]'
)
self.assertEqual(response.data['is_active'], False)

def test_add_membership_for_preexisting_user(self):
# This test covers a bug found by QA where adding a membership
# for a pre-existing user was triggering a 500 server error

view = CommitteeMembershipViewSet()
request = self.factory.get("/api/v1/committee-members/add-member")
request.user = self.user
request.session = {
"committee_uuid": UUID('11111111-2222-3333-4444-555555555555')
}
request.method = "POST"
request.data = {
"role": Membership.CommitteeRole.COMMITTEE_ADMINISTRATOR,
"email": '[email protected]'
}
view.request = request
response = view.add_member(request)

self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['email'], '[email protected]')
self.assertEqual(response.data['is_active'], True)

def test_add_membership_requires_correct_parameters(self):
view = CommitteeMembershipViewSet()
request = self.factory.get("/api/v1/committee-members/add-member")
request.user = self.user
request.session = {
"committee_uuid": UUID('11111111-2222-3333-4444-555555555555')
}
request.method = "POST"

request.data = {
"role": Membership.CommitteeRole.COMMITTEE_ADMINISTRATOR,
}
view.request = request
response = view.add_member(request)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data, 'Missing fields: email')

request.data = {
"email": "[email protected]"
}
view.request = request
response = view.add_member(request)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data, 'Missing fields: role')

request.data = {
"email": "[email protected]",
"role": "A Random String"
}
view.request = request
response = view.add_member(request)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.data, 'Invalid role')

request.data = {
"email": "[email protected]",
"role": Membership.CommitteeRole.COMMITTEE_ADMINISTRATOR
}
view.request = request
response = view.add_member(request)
self.assertEqual(response.status_code, 400)
self.assertEqual(
response.data,
"This email is taken by an existing membership to this committee"
)
10 changes: 6 additions & 4 deletions django-backend/fecfiler/committee_accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CommitteeViewSet
from .views import CommitteeMembershipViewSet, CommitteeViewSet

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r"", CommitteeViewSet, basename="committees")
router.register(r"committees", CommitteeViewSet, basename="committees")
router.register(r"committee-members", CommitteeMembershipViewSet,
basename="committee-members")

# The API URLs are now determined automatically by the router.
urlpatterns = [path("committees/", include(router.urls))]
urlpatterns = []
urlpatterns += router.urls
Loading

0 comments on commit 8b96871

Please sign in to comment.