Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NN API Route #90

Merged
merged 33 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a665053
First stab at NN API route
WillNilges Dec 10, 2023
f29c48a
Add more tests
WillNilges Dec 10, 2023
8ef1d1d
Add more robust test
WillNilges Dec 10, 2023
ea42376
Andy comments 1/n
WillNilges Dec 20, 2023
f2afed1
Refactor to NetworkNumberAssignment
WillNilges Dec 22, 2023
1b52e9d
Hardcode highest network number
WillNilges Dec 22, 2023
998a69f
Remove redundant Request fields
WillNilges Dec 22, 2023
f3154fe
Update Models, remove Request
WillNilges Dec 22, 2023
7a5309a
Swap request for install
WillNilges Dec 22, 2023
9861ee2
Update status and find an install number
WillNilges Dec 22, 2023
8cc7cc5
Make tests work again
WillNilges Dec 22, 2023
d1230ef
Add member moved test
WillNilges Dec 22, 2023
9a46539
Update NN route
WillNilges Dec 22, 2023
3cd2449
Update join form and nn form
WillNilges Dec 23, 2023
7920112
andrew updates 1
WillNilges Dec 23, 2023
90242f3
Andrew changes 2
WillNilges Dec 23, 2023
22c39c4
ugh
WillNilges Dec 23, 2023
ff46c6f
blargh
WillNilges Dec 23, 2023
f254ce5
I hate computers
WillNilges Dec 23, 2023
0e014b6
Tests pass
WillNilges Dec 24, 2023
ce85c56
fuck
WillNilges Dec 24, 2023
782e81d
make big test work
WillNilges Dec 24, 2023
ed45273
Update member object creation
WillNilges Dec 24, 2023
85aaa82
datetime
WillNilges Dec 24, 2023
360be60
squash part 1
WillNilges Dec 24, 2023
4dc0540
nuke migrations
WillNilges Dec 24, 2023
b1acc3a
nuke migrations II electic boogalooo
WillNilges Dec 24, 2023
8e97acc
timezone
WillNilges Dec 24, 2023
cedc588
black
WillNilges Dec 24, 2023
eb72341
Bring back referral field
WillNilges Dec 24, 2023
eb0ef31
andrew comments 4
WillNilges Dec 24, 2023
962a95a
migrations
WillNilges Dec 24, 2023
6573316
make tests pass
WillNilges Dec 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Generated by Django 4.2.5 on 2023-12-22 04:53

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("meshapi", "0016_create_default_groups"),
]

operations = [
migrations.AddField(
model_name="building",
name="node_name",
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name="building",
name="secondary_nn",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.IntegerField(blank=True, null=True), null=True, size=None
),
),
migrations.AddField(
model_name="install",
name="notes",
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name="install",
name="referral",
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name="install",
name="request_date",
field=models.DateField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name="install",
name="roof_access",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="install",
name="ticket_id",
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name="member",
name="secondary_emails",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.EmailField(max_length=254), null=True, size=None
),
),
migrations.AlterField(
model_name="install",
name="install_status",
field=models.IntegerField(
choices=[(0, "Open"), (1, "Scheduled"), (2, "Blocked"), (3, "Active"), (4, "Inactive"), (5, "Closed")]
),
),
migrations.DeleteModel(
name="Request",
),
]
56 changes: 33 additions & 23 deletions src/meshapi/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from django.db import models
from django.contrib.postgres.fields import ArrayField

from django.contrib.auth.models import Group
from django.db.models.aggregates import IntegerField
from django.db.models.fields import EmailField

NETWORK_NUMBER_MAX = 8000
WillNilges marked this conversation as resolved.
Show resolved Hide resolved


class Installer(Group):
Expand All @@ -25,6 +30,8 @@ class BuildingStatus(models.IntegerChoices):
longitude = models.FloatField()
altitude = models.FloatField()
network_number = models.IntegerField(blank=True, null=True)
secondary_nn = ArrayField(IntegerField(blank=True, null=True), null=True)
WillNilges marked this conversation as resolved.
Show resolved Hide resolved
node_name = models.TextField(default=None, blank=True, null=True)
install_date = models.DateField(default=None, blank=True, null=True)
abandon_date = models.DateField(default=None, blank=True, null=True)

Expand All @@ -33,38 +40,41 @@ class Member(models.Model):
first_name = models.TextField()
last_name = models.TextField()
email_address = models.EmailField()
phone_number = models.TextField(
default=None, blank=True, null=True
) # TODO (willnilges): Can we get some validation on this?
secondary_emails = ArrayField(EmailField(), null=True)
phone_number = models.TextField(default=None, blank=True, null=True)
slack_handle = models.TextField(default=None, blank=True, null=True)


class Install(models.Model):
class InstallStatus(models.IntegerChoices):
PLANNED = 0
INACTIVE = 1
ACTIVE = 2
OPEN = 0
SCHEDULED = 1
BLOCKED = 2
ACTIVE = 3
INACTIVE = 4
CLOSED = 5

install_number = models.IntegerField()
# Summary status of install
install_status = models.IntegerField(choices=InstallStatus.choices)
building_id = models.ForeignKey(Building, on_delete=models.PROTECT)
unit = models.TextField(default=None, blank=True, null=True)
member_id = models.ForeignKey(Member, on_delete=models.PROTECT)
install_date = models.DateField(default=None, blank=True, null=True)
abandon_date = models.DateField(default=None, blank=True, null=True)


class Request(models.Model):
class RequestStatus(models.IntegerChoices):
OPEN = 0
CLOSED = 1
INSTALLED = 2
# Install Number (generated when form is submitted)
install_number = models.IntegerField()
WillNilges marked this conversation as resolved.
Show resolved Hide resolved

request_status = models.IntegerField(choices=RequestStatus.choices)
roof_access = models.BooleanField(default=False)
referral = models.TextField(default=None, blank=True, null=True)
# OSTicket ID
ticket_id = models.IntegerField(blank=True, null=True)
member_id = models.ForeignKey(Member, on_delete=models.PROTECT)

# Important dates
request_date = models.DateField(default=None, blank=True, null=True)
Andrew-Dickinson marked this conversation as resolved.
Show resolved Hide resolved
install_date = models.DateField(default=None, blank=True, null=True)
abandon_date = models.DateField(default=None, blank=True, null=True)

# Relation to Building
building_id = models.ForeignKey(Building, on_delete=models.PROTECT)
WillNilges marked this conversation as resolved.
Show resolved Hide resolved
unit = models.TextField(default=None, blank=True, null=True)
install_id = models.ForeignKey(Install, on_delete=models.PROTECT, blank=True, null=True)
roof_access = models.BooleanField(default=False)

# Relation to Member
member_id = models.ForeignKey(Member, on_delete=models.PROTECT)
referral = models.TextField(default=None, blank=True, null=True)

notes = models.TextField(default=None, blank=True, null=True)
23 changes: 4 additions & 19 deletions src/meshapi/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,8 @@ def has_permission(self, request, view):
return True


# Anyone can view requests, but only installers and admins can create them
class RequestListCreatePermissions(permissions.BasePermission):
class NetworkNumberAssignmentPermissions(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == "GET":
return True
else:
if not request.user.is_superuser or is_admin(request.user):
raise PermissionDenied(perm_denied_generic_msg)
return True


# Anyone can retrieve requests, but only an admin can do anything else
class RequestRetrieveUpdateDestroyPermissions(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == "GET":
return True
else:
if not request.user.is_superuser or is_admin(request.user):
raise PermissionDenied(perm_denied_generic_msg)
return True
if not (request.user.is_superuser or is_admin(request.user) or is_installer(request.user)):
raise PermissionDenied(perm_denied_generic_msg)
return True
8 changes: 1 addition & 7 deletions src/meshapi/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.contrib.auth.models import User
from rest_framework import serializers
from meshapi.models import Building, Member, Install, Request
from meshapi.models import Building, Member, Install


class UserSerializer(serializers.ModelSerializer):
Expand All @@ -25,9 +25,3 @@ class InstallSerializer(serializers.ModelSerializer):
class Meta:
model = Install
fields = "__all__"


class RequestSerializer(serializers.ModelSerializer):
class Meta:
model = Request
fields = "__all__"
18 changes: 8 additions & 10 deletions src/meshapi/tests/sample_data.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from meshapi.models import Install, Request
from meshapi.models import Install

sample_member = {
"first_name": "John",
Expand All @@ -24,18 +24,16 @@
}

sample_install = {
"install_number": 420,
"install_status": Install.InstallStatus.ACTIVE,
"install_number": 420,
"ticket_id": 69,
"request_date": "2022-02-27",
"install_date": "2022-03-01",
"abandon_date": "",
"member_id": 1,
"building_id": 1,
}

sample_request = {
"request_status": Request.RequestStatus.OPEN,
"ticket_id": 1,
"unit": 3,
"roof_access": True,
"member_id": 1,
"building_id": 1,
"install_id": "",
"referral": "Read about it on the internet",
"notes": "",
}
59 changes: 53 additions & 6 deletions src/meshapi/tests/test_join_form.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from django.contrib.auth.models import User
from django.test import TestCase, Client
from meshapi.models import Building, Member, Install, Request
from meshapi.models import Building, Member, Install
from meshapi.views import JoinFormRequest

from .sample_join_form_data import *
Expand Down Expand Up @@ -42,15 +42,15 @@ def validate_successful_join_form_submission(test_case, test_name, s, response):
f"Didn't find created building for {test_name}. Should be {length}, but got {len(existing_buildings)}",
)

# Check that a request was created
request_id = json.loads(response.content.decode("utf-8"))["request_id"]
join_form_requests = Request.objects.filter(pk=request_id)
# Check that a install was created
install_id = json.loads(response.content.decode("utf-8"))["install_id"]
join_form_installs = Install.objects.filter(pk=install_id)

length = 1
test_case.assertEqual(
len(join_form_requests),
len(join_form_installs),
length,
f"Didn't find created request for {test_name}. Should be {length}, but got {len(join_form_requests)}",
f"Didn't find created install for {test_name}. Should be {length}, but got {len(join_form_installs)}",
)


Expand Down Expand Up @@ -189,3 +189,50 @@ def test_bad_address_join_form(self):
response.content.decode("utf-8"),
f"Did not get correct response content for bad address join form: {response.content.decode('utf-8')}",
)

def test_member_moved_join_form(self):
# Name, email, phone, location, apt, rooftop, referral
response = self.c.post("/api/v1/join/", valid_join_form_submission, content_type="application/json")

code = 201
self.assertEqual(
code,
response.status_code,
f"status code incorrect for Valid Join Form. Should be {code}, but got {response.status_code}.\n Response is: {response.content.decode('utf-8')}",
)

# Make sure that we get the right stuff out of the database afterwards
s = JoinFormRequest(**valid_join_form_submission)

# Match the format from OSM. I did this to see how OSM would mutate the
# raw request we get.
s.street_address = "151 Broome Street"
s.city = "Manhattan"
s.state = "New York"

validate_successful_join_form_submission(self, "Valid Join Form", s, response)

# Now test that the member can "move" and still access the jon form
form = valid_join_form_submission.copy()
form["street_address"] = "152 Broome Street"

# Name, email, phone, location, apt, rooftop, referral
response = self.c.post("/api/v1/join/", form, content_type="application/json")

code = 201
self.assertEqual(
code,
response.status_code,
f"status code incorrect for Valid Join Form. Should be {code}, but got {response.status_code}.\n Response is: {response.content.decode('utf-8')}",
)

# Make sure that we get the right stuff out of the database afterwards
s = JoinFormRequest(**valid_join_form_submission)

# Match the format from OSM. I did this to see how OSM would mutate the
# raw request we get.
s.street_address = "152 Broome Street"
s.city = "Manhattan"
s.state = "New York"

validate_successful_join_form_submission(self, "Valid Join Form", s, response)
Loading
Loading