diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 463b5d2f6..b2485592a 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -1,5 +1,5 @@
---
-name: ':bug: Bug Report'
+name: '🐞 Bug Report'
about: 'Report an issue.'
title: ''
labels: 'bug'
diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md
index cd13edb7e..cca855004 100644
--- a/.github/ISSUE_TEMPLATE/other.md
+++ b/.github/ISSUE_TEMPLATE/other.md
@@ -1,5 +1,5 @@
---
-name: ':nut_and_bolt: Something else'
+name: 'Something Else'
about: 'Questions, discussions, chores, or other'
title: ''
labels: 'chore'
diff --git a/client/src/App.tsx b/client/src/App.tsx
index eea5203e0..8303346a9 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -18,6 +18,7 @@ import './App.scss';
import { getProfile } from 'store/rcraProfileSlice';
import { selectRcraProfile } from 'store/rcraProfileSlice';
import { selectUserName } from 'store/userSlice';
+import { getHaztrakUser } from 'store/userSlice/user.slice';
function App(): ReactElement {
const userName = useAppSelector(selectUserName);
@@ -28,6 +29,7 @@ function App(): ReactElement {
useEffect(() => {
if (userName) {
dispatch(getProfile());
+ dispatch(getHaztrakUser());
}
}, [profile.user]);
diff --git a/client/src/components/Ht/HtForm.tsx b/client/src/components/Ht/HtForm.tsx
index 755418987..a1ad728e6 100644
--- a/client/src/components/Ht/HtForm.tsx
+++ b/client/src/components/Ht/HtForm.tsx
@@ -32,7 +32,7 @@ HtForm.InputGroup = function (props: InputGroupProps): ReactElement {
HtForm.Label = function (props: FormLabelProps): ReactElement {
return (
-
+
{props.children}
);
diff --git a/client/src/components/Nav/Sidebar.spec.tsx b/client/src/components/Nav/Sidebar.spec.tsx
index 98b70f692..cefeecbe7 100644
--- a/client/src/components/Nav/Sidebar.spec.tsx
+++ b/client/src/components/Nav/Sidebar.spec.tsx
@@ -9,17 +9,17 @@ afterEach(() => {
describe('Sidebar', () => {
test('renders when use is logged in', () => {
- const userName = 'testuser1';
+ const username = 'testuser1';
renderWithProviders(, {
preloadedState: {
user: {
- user: userName,
+ user: { username: username, isLoading: false },
token: 'fakeToken',
loading: false,
},
},
});
- expect(screen.getByText(userName)).toBeInTheDocument();
+ expect(screen.getByText(username)).toBeInTheDocument();
});
test('returns nothing when user not logged in', () => {
const userName = 'testuser1';
diff --git a/client/src/components/Nav/Sidebar.tsx b/client/src/components/Nav/Sidebar.tsx
index f664f5100..4bb46be77 100644
--- a/client/src/components/Nav/Sidebar.tsx
+++ b/client/src/components/Nav/Sidebar.tsx
@@ -147,7 +147,7 @@ export function Sidebar(): ReactElement | null {
Logged in as:
- {authUser}
+ {authUser.username}
diff --git a/client/src/components/Nav/TopNav.spec.tsx b/client/src/components/Nav/TopNav.spec.tsx
index e915df6a1..24eb9f2c3 100644
--- a/client/src/components/Nav/TopNav.spec.tsx
+++ b/client/src/components/Nav/TopNav.spec.tsx
@@ -9,11 +9,11 @@ afterEach(() => {
describe('TopNav', () => {
test('renders when user is logged in', () => {
- const userName = 'testuser1';
+ const username = 'testuser1';
renderWithProviders(, {
preloadedState: {
user: {
- user: userName,
+ user: { username: username, isLoading: false },
token: 'fakeToken',
loading: false,
},
diff --git a/client/src/features/home/Home.spec.tsx b/client/src/features/home/Home.spec.tsx
index 5a7967f1f..789df382f 100644
--- a/client/src/features/home/Home.spec.tsx
+++ b/client/src/features/home/Home.spec.tsx
@@ -22,7 +22,7 @@ describe('Home', () => {
renderWithProviders(, {
preloadedState: {
user: {
- user: username,
+ user: { username: username, isLoading: false },
token: 'fake_token',
loading: false,
error: undefined,
diff --git a/client/src/features/login/Login.tsx b/client/src/features/login/Login.tsx
index 36cf49b16..46379ac32 100644
--- a/client/src/features/login/Login.tsx
+++ b/client/src/features/login/Login.tsx
@@ -5,7 +5,7 @@ import { useForm } from 'react-hook-form';
import { login, useAppDispatch, useAppSelector } from 'store';
import { useNavigate } from 'react-router-dom';
import { useTitle } from 'hooks';
-import { selectUser } from 'store/userSlice';
+import { selectUserState } from 'store/userSlice';
import { z } from 'zod';
import { Col, Container, Form, Row } from 'react-bootstrap';
import logo from 'assets/haztrak-logos/low-resolution/svg/haztrak-low-resolution-logo-black-on-transparent-background.svg';
@@ -24,7 +24,7 @@ type LoginSchema = z.infer;
export function Login(): ReactElement {
useTitle('Login');
const dispatch = useAppDispatch();
- const user = useAppSelector(selectUser);
+ const userState = useAppSelector(selectUserState);
const navigation = useNavigate();
const {
register,
@@ -34,10 +34,10 @@ export function Login(): ReactElement {
useEffect(() => {
// redirect to home if already logged in
- if (user.user) {
+ if (userState.user?.username) {
navigation('/');
}
- }, [user.user]);
+ }, [userState.user?.username]);
function onSubmit({ username, password }: LoginSchema) {
return dispatch(login({ username, password }));
@@ -86,8 +86,8 @@ export function Login(): ReactElement {
{isSubmitting && }
Login
- {user.error && (
- {String(user.error)}
+ {userState.error && (
+ {String(userState.error)}
)}
diff --git a/client/src/features/profile/Profile.tsx b/client/src/features/profile/Profile.tsx
index 9c8aedde8..1015ebae4 100644
--- a/client/src/features/profile/Profile.tsx
+++ b/client/src/features/profile/Profile.tsx
@@ -1,4 +1,5 @@
import { HtCard } from 'components/Ht';
+import { HaztrakUser, selectUser } from 'store/userSlice/user.slice';
import { RcraProfile } from './RcraProfile';
import { UserProfile } from './UserProfile';
import { useTitle } from 'hooks';
@@ -14,8 +15,13 @@ import { getProfile, selectRcraProfile } from 'store/rcraProfileSlice';
export function Profile(): ReactElement {
const dispatch = useAppDispatch();
const profile = useAppSelector(selectRcraProfile);
+ const user: HaztrakUser | undefined = useAppSelector(selectUser);
useTitle('Profile');
+ if (!user) {
+ return loading...
;
+ }
+
useEffect(() => {
dispatch(getProfile());
}, [profile.user]);
@@ -31,7 +37,7 @@ export function Profile(): ReactElement {
-
+
diff --git a/client/src/features/profile/RcraProfile.tsx b/client/src/features/profile/RcraProfile.tsx
index 4b0f1d21a..0d8299aeb 100644
--- a/client/src/features/profile/RcraProfile.tsx
+++ b/client/src/features/profile/RcraProfile.tsx
@@ -15,7 +15,6 @@ interface ProfileViewProps {
profile: RcraProfileState;
}
-// ToDo: Each field should be empty or meet the min length requirements
// ToDo: Either rcraAPIId & rcraAPIID should both be empty or both be non-empty
const rcraProfileForm = z.object({
rcraAPIID: z.string().min(36).optional(),
@@ -28,7 +27,7 @@ type RcraProfileForm = z.infer;
export function RcraProfile({ profile }: ProfileViewProps) {
const [editable, setEditable] = useState(false);
const [profileLoading, setProfileLoading] = useState(false);
- const { error, rcraSites, loading, ...formValues } = profile;
+ const { rcraSites, loading, ...formValues } = profile;
const dispatch = useAppDispatch();
const {
diff --git a/client/src/features/profile/UserProfile.spec.tsx b/client/src/features/profile/UserProfile.spec.tsx
new file mode 100644
index 000000000..3dd7e02b1
--- /dev/null
+++ b/client/src/features/profile/UserProfile.spec.tsx
@@ -0,0 +1,65 @@
+import { cleanup } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { UserProfile } from 'features/profile/UserProfile';
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+import React from 'react';
+import { HaztrakUser } from 'store/userSlice/user.slice';
+import { renderWithProviders, screen } from 'test-utils';
+import { API_BASE_URL } from 'test-utils/mock/handlers';
+import { vi } from 'vitest';
+
+const DEFAULT_USER: HaztrakUser = {
+ username: 'test',
+ firstName: 'David',
+ lastName: 'smith',
+ email: 'test@mail.com',
+};
+
+const server = setupServer(
+ rest.put(`${API_BASE_URL}/api/user/`, (req, res, ctx) => {
+ const user: HaztrakUser = { ...DEFAULT_USER };
+ // @ts-ignore
+ return res(ctx.status(200), ctx.json({ ...user, ...req.body }));
+ })
+);
+
+// pre-/post-test hooks
+beforeAll(() => server.listen());
+afterEach(() => {
+ server.resetHandlers();
+ cleanup();
+ vi.resetAllMocks();
+});
+afterAll(() => server.close()); // Disable API mocking after the tests are done.
+
+describe('UserProfile', () => {
+ test('renders', () => {
+ const user: HaztrakUser = {
+ ...DEFAULT_USER,
+ username: 'test',
+ firstName: 'David',
+ };
+ renderWithProviders(, {});
+ expect(screen.getByRole('textbox', { name: 'First Name' })).toHaveValue(user.firstName);
+ expect(screen.getByText(user.username)).toBeInTheDocument();
+ });
+ test('update profile fields', async () => {
+ // Arrange
+ const newEmail = 'newMockEmail@mail.com';
+ const user: HaztrakUser = {
+ ...DEFAULT_USER,
+ };
+ renderWithProviders(, {});
+ const editButton = screen.getByRole('button', { name: 'Edit' });
+ const emailTextBox = screen.getByRole('textbox', { name: 'Email' });
+ // Act
+ await userEvent.click(editButton);
+ await userEvent.clear(emailTextBox);
+ await userEvent.type(emailTextBox, newEmail);
+ const saveButton = screen.getByRole('button', { name: 'Save' });
+ await userEvent.click(saveButton);
+ // Assert
+ expect(await screen.findByRole('textbox', { name: 'Email' })).toHaveValue(newEmail);
+ });
+});
diff --git a/client/src/features/profile/UserProfile.tsx b/client/src/features/profile/UserProfile.tsx
index 0b3320afa..9c0c852b8 100644
--- a/client/src/features/profile/UserProfile.tsx
+++ b/client/src/features/profile/UserProfile.tsx
@@ -1,21 +1,155 @@
-import React from 'react';
-import { Button, Container } from 'react-bootstrap';
-import { RcraProfileState } from 'store/rcraProfileSlice/rcraProfile.slice';
+import { faUser } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { HtForm } from 'components/Ht';
+import React, { createRef, useState } from 'react';
+import { Button, Col, Container, Form, Row } from 'react-bootstrap';
+import { useForm } from 'react-hook-form';
+import { htApi } from 'services';
+import { useAppDispatch } from 'store';
+import { HaztrakUser, updateUserProfile } from 'store/userSlice/user.slice';
+import { z } from 'zod';
-interface ProfileViewProps {
- profile: RcraProfileState;
+interface UserProfileProps {
+ user: HaztrakUser;
}
-export function UserProfile({ profile }: ProfileViewProps) {
+const haztrakUserForm = z.object({
+ firstName: z.string().min(4, 'First name must be a least 4 character(s)').optional(),
+ lastName: z.string().min(5, 'Last name must be at least 5 character(s)').optional(),
+ email: z.string().email('Not a valid email address').optional(),
+});
+
+export function UserProfile({ user }: UserProfileProps) {
+ const [editable, setEditable] = useState(false);
+ const fileRef = createRef();
+ const dispatch = useAppDispatch();
+
+ const {
+ register,
+ reset,
+ handleSubmit,
+ formState: { errors },
+ } = useForm({ values: user, resolver: zodResolver(haztrakUserForm) });
+
+ const onSubmit = (data: any) => {
+ setEditable(!editable);
+ htApi
+ .put('/user/', data)
+ .then((r) => {
+ dispatch(updateUserProfile(r.data));
+ })
+ .catch((r) => console.error(r));
+ };
+
return (
- <>
-
- Username
- {profile.user}
-
-
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
{user.username}
+
+
+
+
+
+
+
+ First Name
+
+ {errors.firstName?.message}
+
+
+
+
+ Last Name
+
+ {errors.lastName?.message}
+
+
+
+
+
+
+ Email
+
+ {errors.email?.message}
+
+
+
+
+
+ {!editable ? (
+ <>
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+
+
);
}
diff --git a/client/src/store/rcraProfileSlice/rcraProfile.slice.ts b/client/src/store/rcraProfileSlice/rcraProfile.slice.ts
index cb65e5bbc..76f25e8c5 100644
--- a/client/src/store/rcraProfileSlice/rcraProfile.slice.ts
+++ b/client/src/store/rcraProfileSlice/rcraProfile.slice.ts
@@ -5,6 +5,7 @@
import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
import axios from 'axios';
import { HaztrakSite } from 'components/HaztrakSite';
+import { htApi } from 'services';
import { RootState } from 'store';
/**
@@ -95,8 +96,8 @@ export const getProfile = createAsyncThunk(
'rcraProfile/getProfile',
async (arg, thunkAPI) => {
const state = thunkAPI.getState() as RootState;
- const username = state.user.user;
- const response = await axios.get(`${import.meta.env.VITE_HT_API_URL}/api/profile/${username}`);
+ const username = state.user.user?.username;
+ const response = await htApi.get(`${import.meta.env.VITE_HT_API_URL}/api/profile/${username}`);
const { rcraSites, ...rest } = response.data as RcraProfileResponse;
// Convert the array of RcraSite permissions we get from our backend
// to an object which each key corresponding to the RcraSite's ID number
diff --git a/client/src/store/userSlice/index.ts b/client/src/store/userSlice/index.ts
index 8939923e7..b7d291a23 100644
--- a/client/src/store/userSlice/index.ts
+++ b/client/src/store/userSlice/index.ts
@@ -1,4 +1,4 @@
-import userReducer, { login, selectUserName, selectUser } from './user.slice';
+import userReducer, { login, selectUserName, selectUser, selectUserState } from './user.slice';
export default userReducer;
-export { login, selectUserName, selectUser };
+export { login, selectUserName, selectUser, selectUserState };
diff --git a/client/src/store/userSlice/user.slice.ts b/client/src/store/userSlice/user.slice.ts
index 80e92d173..333966e48 100644
--- a/client/src/store/userSlice/user.slice.ts
+++ b/client/src/store/userSlice/user.slice.ts
@@ -1,13 +1,22 @@
-import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit';
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import axios from 'axios';
-import { RcraProfileSite, RcraProfileState } from 'store/rcraProfileSlice/rcraProfile.slice';
+import { htApi } from 'services';
import { RootState } from 'store/rootStore';
+export interface HaztrakUser {
+ username: string;
+ email?: string;
+ firstName?: string;
+ lastName?: string;
+ isLoading?: boolean;
+ error?: string;
+}
+
/**
* The Redux stored information on the current haztrak user
*/
export interface UserState {
- user?: string;
+ user?: HaztrakUser;
token?: string;
loading?: boolean;
error?: string;
@@ -15,7 +24,7 @@ export interface UserState {
const initialState: UserState = {
// Retrieve the user's username and token from local storage. For convenience
- user: JSON.parse(localStorage.getItem('user') || 'null') || null,
+ user: { username: JSON.parse(localStorage.getItem('user') || 'null') || null, isLoading: false },
token: JSON.parse(localStorage.getItem('token') || 'null') || null,
loading: false,
error: undefined,
@@ -28,10 +37,23 @@ export const login = createAsyncThunk(
username,
password,
});
- return response.data as UserState;
+ // return response.data as UserState;
+ return {
+ user: { username: response.data.user },
+ token: response.data.token,
+ } as UserState;
}
);
+export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => {
+ const response = await htApi.get(`${import.meta.env.VITE_HT_API_URL}/api/user`);
+ if (response.status >= 200 && response.status < 300) {
+ return response.data as HaztrakUser;
+ } else {
+ return thunkAPI.rejectWithValue(response.data);
+ }
+});
+
/**
* User logout Redux reducer Function
*
@@ -47,11 +69,21 @@ function logout(user: UserState) {
return { ...initialState, user: undefined, token: undefined } as UserState;
}
+/**
+ * update the HaztrakUser state with the new user information
+ */
+
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
logout,
+ updateUserProfile(state: UserState, action: any) {
+ return {
+ ...state,
+ user: action.payload,
+ };
+ },
},
extraReducers: (builder) => {
builder
@@ -67,7 +99,7 @@ const userSlice = createSlice({
// Todo: currently, we store username and jwt token in local storage so
// the user stays logged in between page refreshes. This is a known vulnerability to be
// fixed in the future. For now, it's a development convenience.
- localStorage.setItem('user', JSON.stringify(authResponse.user));
+ localStorage.setItem('user', JSON.stringify(authResponse.user?.username));
localStorage.setItem('token', JSON.stringify(authResponse.token));
return {
loading: false,
@@ -82,6 +114,28 @@ const userSlice = createSlice({
error: action.payload.error,
loading: false,
};
+ })
+ .addCase(getHaztrakUser.pending, (state) => {
+ return {
+ ...state,
+ error: undefined,
+ loading: true,
+ };
+ })
+ .addCase(getHaztrakUser.rejected, (state, action) => {
+ return {
+ ...state,
+ error: `Error: ${action.payload}`,
+ loading: true,
+ };
+ })
+ .addCase(getHaztrakUser.fulfilled, (state, action) => {
+ return {
+ ...state,
+ user: action.payload,
+ error: undefined,
+ loading: true,
+ };
});
},
});
@@ -89,11 +143,17 @@ const userSlice = createSlice({
/**
* Get the current user's username from the Redux store
*/
-export const selectUserName = (state: RootState) => state.user.user;
+export const selectUserName = (state: RootState): string | undefined => state.user.user?.username;
/**
* Select the current user
*/
-export const selectUser = (state: RootState) => state.user;
+export const selectUser = (state: RootState): HaztrakUser | undefined => state.user.user;
+
+/**
+ * Select the current User State
+ */
+export const selectUserState = (state: RootState): UserState => state.user;
export default userSlice.reducer;
+export const { updateUserProfile } = userSlice.actions;
diff --git a/client/src/store/userSlice/user.spec.ts b/client/src/store/userSlice/user.spec.ts
index ba7aeb705..c40ddfc3c 100644
--- a/client/src/store/userSlice/user.spec.ts
+++ b/client/src/store/userSlice/user.spec.ts
@@ -12,7 +12,7 @@ const initialState: UserState = {
};
const userPayload: UserState = {
- user: 'testuser1',
+ user: { username: 'testuser1' },
token: 'mockToken',
};
diff --git a/docs/haztrak_book/src/assets/erd.png b/docs/haztrak_book/src/assets/erd.png
new file mode 100644
index 000000000..af299cca3
Binary files /dev/null and b/docs/haztrak_book/src/assets/erd.png differ
diff --git a/runhaz.sh b/runhaz.sh
index 17ebce6b5..891e37d26 100755
--- a/runhaz.sh
+++ b/runhaz.sh
@@ -87,7 +87,7 @@ generate_api_schema() {
graph_models() {
print_style "Generating Entity Relationship Diagram...\n" "success";
- exec_cmd="$base_py_cmd graph_models sites trak \
+ exec_cmd="$base_py_cmd graph_models sites trak core\
-g \
--settings haztrak.settings \
--rankdir=RL \
diff --git a/server/apps/conftest.py b/server/apps/conftest.py
index 75f8213d0..9032bdb32 100644
--- a/server/apps/conftest.py
+++ b/server/apps/conftest.py
@@ -11,12 +11,11 @@
from django.contrib.auth.models import User
from rest_framework.test import APIClient
-from apps.core.models import HaztrakUser
+from apps.core.models import HaztrakUser, RcraProfile
from apps.sites.models import (
Address,
Contact,
RcraPhone,
- RcraProfile,
RcraSite,
Site,
)
diff --git a/server/apps/core/migrations/0001_initial.py b/server/apps/core/migrations/0001_initial.py
index 3c7dc83db..62a8fe5f5 100644
--- a/server/apps/core/migrations/0001_initial.py
+++ b/server/apps/core/migrations/0001_initial.py
@@ -1,8 +1,10 @@
-# Generated by Django 4.1.7 on 2023-05-06 13:55
+# Generated by Django 4.2.1 on 2023-06-12 18:32
+from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
+import django.db.models.deletion
import django.utils.timezone
@@ -113,4 +115,29 @@ class Migration(migrations.Migration):
("objects", django.contrib.auth.models.UserManager()),
],
),
+ migrations.CreateModel(
+ name="RcraProfile",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ ("rcra_api_key", models.CharField(blank=True, max_length=128, null=True)),
+ ("rcra_api_id", models.CharField(blank=True, max_length=128, null=True)),
+ ("rcra_username", models.CharField(blank=True, max_length=128, null=True)),
+ ("phone_number", models.CharField(blank=True, max_length=15, null=True)),
+ ("email", models.EmailField(max_length=254)),
+ (
+ "user",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
+ ),
+ ),
+ ],
+ options={
+ "ordering": ["rcra_username"],
+ },
+ ),
]
diff --git a/server/apps/core/models.py b/server/apps/core/models.py
index ef61624d3..bc5f0d494 100644
--- a/server/apps/core/models.py
+++ b/server/apps/core/models.py
@@ -1,5 +1,80 @@
from django.contrib.auth.models import AbstractUser
+from django.db import models
+
+from haztrak import settings
+
+
+class CoreBaseModel(models.Model):
+ """Base class for all apps.core models"""
+
+ class Meta:
+ abstract = True
+ ordering = ["pk"]
+
+ def __str__(self):
+ return f"{self.__class__.__name__}"
+
+ def __repr__(self):
+ field_values = ", ".join(
+ f"{field.name}={getattr(self, field.name)!r}" for field in self._meta.fields
+ )
+ return f"<{self.__class__.__name__}({field_values})>"
class HaztrakUser(AbstractUser):
+ """Haztrak abstract user model. It simply inherits from Django's AbstractUser model."""
+
pass
+
+
+class RcraProfile(CoreBaseModel):
+ """
+ Contains a user's RcraProfile information, such as username, and API credentials.
+ Has a one-to-one relationship with the User model.
+ """
+
+ class Meta:
+ ordering = ["rcra_username"]
+
+ user = models.OneToOneField(
+ settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE,
+ )
+ rcra_api_key = models.CharField(
+ max_length=128,
+ null=True,
+ blank=True,
+ )
+ rcra_api_id = models.CharField(
+ max_length=128,
+ null=True,
+ blank=True,
+ )
+ rcra_username = models.CharField(
+ max_length=128,
+ null=True,
+ blank=True,
+ )
+ phone_number = models.CharField(
+ max_length=15,
+ null=True,
+ blank=True,
+ )
+ email = models.EmailField()
+
+ def __str__(self):
+ return f"{self.user.username}"
+
+ def sync(self):
+ """Launch task to sync use profile. ToDo: remove this method"""
+ from apps.sites.tasks import sync_user_sites
+
+ task = sync_user_sites.delay(str(self.user.username))
+ return task
+
+ @property
+ def is_api_user(self) -> bool:
+ """Returns true if the use has Rcrainfo API credentials"""
+ if self.rcra_username and self.rcra_api_id and self.rcra_api_key:
+ return True
+ return False
diff --git a/server/apps/core/serializers.py b/server/apps/core/serializers.py
new file mode 100644
index 000000000..75b5bcd03
--- /dev/null
+++ b/server/apps/core/serializers.py
@@ -0,0 +1,79 @@
+from rest_framework import serializers
+from rest_framework.serializers import ModelSerializer
+
+from apps.core.models import HaztrakUser, RcraProfile
+from apps.sites.serializers import RcraSitePermissionSerializer
+
+
+class HaztrakUserSerializer(ModelSerializer):
+ """
+ Model serializer for marshalling/unmarshalling a user's HaztrakUser
+ """
+
+ username = serializers.CharField(
+ required=False,
+ )
+ firstName = serializers.CharField(
+ source="first_name",
+ required=False,
+ )
+ lastName = serializers.CharField(
+ source="last_name",
+ required=False,
+ )
+
+ class Meta:
+ model = HaztrakUser
+ fields = [
+ "username",
+ "firstName",
+ "lastName",
+ "email",
+ ]
+
+
+class RcraProfileSerializer(ModelSerializer):
+ """
+ Model serializer for marshalling/unmarshalling a user's RcraProfile
+ """
+
+ user = serializers.StringRelatedField()
+ rcraSites = RcraSitePermissionSerializer(
+ source="permissions",
+ required=False,
+ many=True,
+ )
+ phoneNumber = serializers.CharField(
+ source="phone_number",
+ required=False,
+ )
+ rcraAPIID = serializers.CharField(
+ source="rcra_api_id",
+ required=False,
+ )
+ rcraAPIKey = serializers.CharField(
+ source="rcra_api_key",
+ required=False,
+ write_only=True,
+ )
+ rcraUsername = serializers.CharField(
+ source="rcra_username",
+ required=False,
+ )
+ apiUser = serializers.BooleanField(
+ source="is_api_user",
+ required=False,
+ allow_null=False,
+ )
+
+ class Meta:
+ model = RcraProfile
+ fields = [
+ "user",
+ "rcraAPIID",
+ "rcraAPIKey",
+ "rcraUsername",
+ "rcraSites",
+ "phoneNumber",
+ "apiUser",
+ ]
diff --git a/server/apps/core/services/rcrainfo_service.py b/server/apps/core/services/rcrainfo_service.py
index a84c42933..3663ccd32 100644
--- a/server/apps/core/services/rcrainfo_service.py
+++ b/server/apps/core/services/rcrainfo_service.py
@@ -4,7 +4,7 @@
from django.db import IntegrityError
from emanifest import RcrainfoClient, RcrainfoResponse
-from apps.sites.models.profile_models import RcraProfile
+from apps.core.models import RcraProfile
from apps.trak.models import WasteCode
diff --git a/server/apps/core/urls.py b/server/apps/core/urls.py
index a1fecd9ed..9c3f970ec 100644
--- a/server/apps/core/urls.py
+++ b/server/apps/core/urls.py
@@ -1,8 +1,21 @@
from django.urls import path
-from .views import ExampleTaskView, Login, TaskStatusView
+from .views import (
+ ExampleTaskView,
+ HaztrakUserView,
+ Login,
+ RcraProfileView,
+ RcraSitePermissionView,
+ SyncProfileView,
+ TaskStatusView,
+)
urlpatterns = [
+ # Rcra Profile
+ path("profile//sync", SyncProfileView.as_view()),
+ path("profile/", RcraProfileView.as_view()),
+ path("site/permission/", RcraSitePermissionView.as_view()),
+ path("user/", HaztrakUserView.as_view()),
path("user/login/", Login.as_view()),
path("task/example", ExampleTaskView.as_view()),
path("task/", TaskStatusView.as_view()),
diff --git a/server/apps/core/views/__init__.py b/server/apps/core/views/__init__.py
index 2c99aff1c..bb9d5b890 100644
--- a/server/apps/core/views/__init__.py
+++ b/server/apps/core/views/__init__.py
@@ -1,2 +1,8 @@
from .auth_view import Login
+from .profile_views import (
+ HaztrakUserView,
+ RcraProfileView,
+ RcraSitePermissionView,
+ SyncProfileView,
+)
from .task_views import ExampleTaskView, TaskStatusView
diff --git a/server/apps/sites/views/profile_views.py b/server/apps/core/views/profile_views.py
similarity index 67%
rename from server/apps/sites/views/profile_views.py
rename to server/apps/core/views/profile_views.py
index 486a97bd8..aeb0e451b 100644
--- a/server/apps/sites/views/profile_views.py
+++ b/server/apps/core/views/profile_views.py
@@ -5,14 +5,26 @@
from rest_framework.request import Request
from rest_framework.response import Response
-from apps.sites.models import RcraProfile, RcraSitePermission
+from apps.core.models import HaztrakUser, RcraProfile
+from apps.core.serializers import HaztrakUserSerializer, RcraProfileSerializer
+from apps.sites.models import RcraSitePermission
from apps.sites.serializers import (
- RcraPermissionSerializer,
- RcraProfileSerializer,
RcraSitePermissionSerializer,
)
+class HaztrakUserView(RetrieveUpdateAPIView):
+ """Retrieve the current user's base information"""
+
+ queryset = HaztrakUser.objects.all()
+ serializer_class = HaztrakUserSerializer
+ permission_classes = [permissions.AllowAny] # ToDo - temporary remove this
+
+ def get_object(self):
+ # return HaztrakUser.objects.get(username="testuser1")
+ return self.request.user
+
+
class RcraProfileView(RetrieveUpdateAPIView):
"""
Responsible for Create/Update operations related to the user RcraProfile,
@@ -25,7 +37,6 @@ class RcraProfileView(RetrieveUpdateAPIView):
response = Response
lookup_field = "user__username"
lookup_url_kwarg = "user"
- permission_classes = [permissions.AllowAny] # temporary, remove me
class SyncProfileView(GenericAPIView):
@@ -49,22 +60,12 @@ def get(self, request: Request, user: str = None) -> Response:
class RcraSitePermissionView(RetrieveAPIView):
"""
- For Viewing a user's Site Permissions in haztrak's internal JSON structure.
- This is not included in the current URL configs, but kept here for documentation.
+ For Viewing the RcraSite Permissions for the given user
"""
queryset = RcraSitePermission.objects.all()
serializer_class = RcraSitePermissionSerializer
- permission_classes = [permissions.AllowAny]
-
-class RcraPermissionView(RetrieveAPIView):
- """
- For Viewing a user's Site Permissions in the same JSON structure as RCRAInfo.
-
- This is not included in the current URL configs, but kept here for documentation.
- """
-
- queryset = RcraSitePermission.objects.all()
- serializer_class = RcraPermissionSerializer
- permission_classes = [permissions.AllowAny]
+ def get_queryset(self):
+ user = self.request.user
+ return RcraSitePermission.objects.filter(profile__user=user)
diff --git a/server/apps/sites/admin.py b/server/apps/sites/admin.py
index 8a472faee..c7a2ae441 100644
--- a/server/apps/sites/admin.py
+++ b/server/apps/sites/admin.py
@@ -3,7 +3,8 @@
from django.utils.html import format_html, urlencode
from apps.core.admin import HiddenListView
-from apps.sites.models import Address, Contact, RcraProfile, RcraSite, RcraSitePermission, Site
+from apps.core.models import RcraProfile
+from apps.sites.models import Address, Contact, RcraSite, RcraSitePermission, Site
@admin.register(RcraSite)
diff --git a/server/apps/sites/migrations/0001_initial.py b/server/apps/sites/migrations/0001_initial.py
index d1506f0bc..ea9a12858 100644
--- a/server/apps/sites/migrations/0001_initial.py
+++ b/server/apps/sites/migrations/0001_initial.py
@@ -1,7 +1,6 @@
-# Generated by Django 4.1.7 on 2023-05-06 13:55
+# Generated by Django 4.2.1 on 2023-06-12 18:32
import apps.sites.models.contact_models
-from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
@@ -11,7 +10,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ("core", "0001_initial"),
]
operations = [
@@ -162,31 +161,6 @@ class Migration(migrations.Migration):
"ordering": ["number"],
},
),
- migrations.CreateModel(
- name="RcraProfile",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
- ),
- ),
- ("rcra_api_key", models.CharField(blank=True, max_length=128, null=True)),
- ("rcra_api_id", models.CharField(blank=True, max_length=128, null=True)),
- ("rcra_username", models.CharField(blank=True, max_length=128, null=True)),
- ("phone_number", models.CharField(blank=True, max_length=15, null=True)),
- ("email", models.EmailField(max_length=254)),
- (
- "user",
- models.OneToOneField(
- on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
- ),
- ),
- ],
- options={
- "ordering": ["rcra_username"],
- },
- ),
migrations.CreateModel(
name="RcraSite",
fields=[
@@ -394,7 +368,7 @@ class Migration(migrations.Migration):
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="permissions",
- to="sites.rcraprofile",
+ to="core.rcraprofile",
),
),
(
diff --git a/server/apps/sites/models/__init__.py b/server/apps/sites/models/__init__.py
index cf5f13125..8f01cbc10 100644
--- a/server/apps/sites/models/__init__.py
+++ b/server/apps/sites/models/__init__.py
@@ -1,3 +1,3 @@
+from ...core.models import RcraProfile
from .contact_models import Address, Contact, RcraPhone, RcraStates
-from .profile_models import RcraProfile, RcraSitePermission
-from .site_models import RcraSite, RcraSiteType, Site, Role
+from .site_models import RcraSite, RcraSitePermission, RcraSiteType, Role, Site
diff --git a/server/apps/sites/models/profile_models.py b/server/apps/sites/models/profile_models.py
deleted file mode 100644
index 8549112ab..000000000
--- a/server/apps/sites/models/profile_models.py
+++ /dev/null
@@ -1,123 +0,0 @@
-from django.core.exceptions import ValidationError
-from django.db import models
-
-from haztrak import settings
-
-from .base_models import SitesBaseModel
-from .site_models import Site
-
-
-class RcraProfile(SitesBaseModel):
- """
- Contains a user's RcraProfile information, such as username, and API credentials.
- Has a one-to-one relationship with the User model.
- """
-
- class Meta:
- ordering = ["rcra_username"]
-
- user = models.OneToOneField(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- )
- rcra_api_key = models.CharField(
- max_length=128,
- null=True,
- blank=True,
- )
- rcra_api_id = models.CharField(
- max_length=128,
- null=True,
- blank=True,
- )
- rcra_username = models.CharField(
- max_length=128,
- null=True,
- blank=True,
- )
- phone_number = models.CharField(
- max_length=15,
- null=True,
- blank=True,
- )
- email = models.EmailField()
-
- def __str__(self):
- return f"{self.user.username}"
-
- def sync(self):
- """Launch task to sync use profile. ToDo: remove this method"""
- from apps.sites.tasks import sync_user_sites
-
- task = sync_user_sites.delay(str(self.user.username))
- return task
-
- @property
- def is_api_user(self) -> bool:
- """Returns true if the use has Rcrainfo API credentials"""
- if self.rcra_username and self.rcra_api_id and self.rcra_api_key:
- return True
- return False
-
-
-class RcraSitePermission(SitesBaseModel):
- """
- RCRAInfo Site Permissions per module connected to a user's RcraProfile
- and the corresponding Site
- """
-
- CERTIFIER = "Certifier"
- PREPARER = "Preparer"
- VIEWER = "Viewer"
-
- EPA_PERMISSION_LEVEL = [
- (CERTIFIER, "Certifier"),
- (PREPARER, "Preparer"),
- (VIEWER, "Viewer"),
- ]
-
- class Meta:
- verbose_name = "RCRA Site Permission"
- ordering = ["site__rcra_site__epa_id"]
-
- site = models.ForeignKey(Site, on_delete=models.CASCADE)
- profile = models.ForeignKey(
- RcraProfile,
- on_delete=models.PROTECT,
- related_name="permissions",
- )
- site_manager = models.BooleanField(
- default=False,
- )
- annual_report = models.CharField(
- max_length=12,
- choices=EPA_PERMISSION_LEVEL,
- )
- biennial_report = models.CharField(
- max_length=12,
- choices=EPA_PERMISSION_LEVEL,
- )
- e_manifest = models.CharField(
- max_length=12,
- choices=EPA_PERMISSION_LEVEL,
- )
- my_rcra_id = models.CharField(
- max_length=12,
- choices=EPA_PERMISSION_LEVEL,
- )
- wiets = models.CharField(
- max_length=12,
- choices=EPA_PERMISSION_LEVEL,
- )
-
- def __str__(self):
- return f"{self.profile.user}: {self.site.rcra_site.epa_id}"
-
- def clean(self):
- if self.site_manager:
- fields = ["annual_report", "biennial_report", "e_manifest", "my_rcra_id", "wiets"]
- for field_name in fields:
- if getattr(self, field_name) != "Certifier":
- raise ValidationError(
- f"The value for the '{field_name}' field must be set to 'Certifier'."
- )
diff --git a/server/apps/sites/models/site_models.py b/server/apps/sites/models/site_models.py
index 36ae2c0bb..1b4cb7b3c 100644
--- a/server/apps/sites/models/site_models.py
+++ b/server/apps/sites/models/site_models.py
@@ -206,7 +206,73 @@ def __str__(self):
class Role(models.TextChoices):
- INDUSTRY = "IN", _("Industry")
- PPC = "PP", _("Ppc")
- EPA = "EP", _("Epa")
- STATE = "ST", _("State")
+ INDUSTRY = "IN", _("Industry")
+ PPC = "PP", _("Ppc")
+ EPA = "EP", _("Epa")
+ STATE = "ST", _("State")
+
+
+class RcraSitePermission(SitesBaseModel):
+ """
+ RCRAInfo Site Permissions per module connected to a user's RcraProfile
+ and the corresponding Site
+ """
+
+ CERTIFIER = "Certifier"
+ PREPARER = "Preparer"
+ VIEWER = "Viewer"
+
+ EPA_PERMISSION_LEVEL = [
+ (CERTIFIER, "Certifier"),
+ (PREPARER, "Preparer"),
+ (VIEWER, "Viewer"),
+ ]
+
+ class Meta:
+ verbose_name = "RCRA Site Permission"
+ ordering = ["site__rcra_site__epa_id"]
+
+ site = models.ForeignKey(
+ Site,
+ on_delete=models.CASCADE,
+ )
+ profile = models.ForeignKey(
+ "core.RcraProfile",
+ on_delete=models.PROTECT,
+ related_name="permissions",
+ )
+ site_manager = models.BooleanField(
+ default=False,
+ )
+ annual_report = models.CharField(
+ max_length=12,
+ choices=EPA_PERMISSION_LEVEL,
+ )
+ biennial_report = models.CharField(
+ max_length=12,
+ choices=EPA_PERMISSION_LEVEL,
+ )
+ e_manifest = models.CharField(
+ max_length=12,
+ choices=EPA_PERMISSION_LEVEL,
+ )
+ my_rcra_id = models.CharField(
+ max_length=12,
+ choices=EPA_PERMISSION_LEVEL,
+ )
+ wiets = models.CharField(
+ max_length=12,
+ choices=EPA_PERMISSION_LEVEL,
+ )
+
+ def __str__(self):
+ return f"{self.profile.user}: {self.site.rcra_site.epa_id}"
+
+ def clean(self):
+ if self.site_manager:
+ fields = ["annual_report", "biennial_report", "e_manifest", "my_rcra_id", "wiets"]
+ for field_name in fields:
+ if getattr(self, field_name) != "Certifier":
+ raise ValidationError(
+ f"The value for the '{field_name}' field must be set to 'Certifier'."
+ )
diff --git a/server/apps/sites/serializers/__init__.py b/server/apps/sites/serializers/__init__.py
index 6a89221d9..68e5eda03 100644
--- a/server/apps/sites/serializers/__init__.py
+++ b/server/apps/sites/serializers/__init__.py
@@ -2,7 +2,6 @@
from .contact_ser import ContactSerializer, RcraPhoneSerializer
from .profile_ser import (
RcraPermissionSerializer,
- RcraProfileSerializer,
RcraSitePermissionSerializer,
)
from .site_ser import RcraSiteSerializer, SiteSerializer
diff --git a/server/apps/sites/serializers/profile_ser.py b/server/apps/sites/serializers/profile_ser.py
index 5e986b796..eb91f8dd1 100644
--- a/server/apps/sites/serializers/profile_ser.py
+++ b/server/apps/sites/serializers/profile_ser.py
@@ -1,8 +1,7 @@
from rest_framework import serializers
from rest_framework.exceptions import APIException
-from rest_framework.serializers import ModelSerializer
-from apps.sites.models.profile_models import RcraProfile, RcraSitePermission
+from apps.sites.models import RcraSitePermission
from .base_ser import SitesBaseSerializer
from .site_ser import SiteSerializer
@@ -163,50 +162,3 @@ class Meta:
"WIETS",
"myRCRAid",
]
-
-
-class RcraProfileSerializer(ModelSerializer):
- """
- Model serializer for marshalling/unmarshalling a user's RcraProfile
- """
-
- user = serializers.StringRelatedField()
- rcraSites = RcraSitePermissionSerializer(
- source="permissions",
- required=False,
- many=True,
- )
- phoneNumber = serializers.CharField(
- source="phone_number",
- required=False,
- )
- rcraAPIID = serializers.CharField(
- source="rcra_api_id",
- required=False,
- )
- rcraAPIKey = serializers.CharField(
- source="rcra_api_key",
- required=False,
- write_only=True,
- )
- rcraUsername = serializers.CharField(
- source="rcra_username",
- required=False,
- )
- apiUser = serializers.BooleanField(
- source="is_api_user",
- required=False,
- allow_null=False,
- )
-
- class Meta:
- model = RcraProfile
- fields = [
- "user",
- "rcraAPIID",
- "rcraAPIKey",
- "rcraUsername",
- "rcraSites",
- "phoneNumber",
- "apiUser",
- ]
diff --git a/server/apps/sites/services/profile_services.py b/server/apps/sites/services/profile_services.py
index a4e11b5f4..d6c1500fd 100644
--- a/server/apps/sites/services/profile_services.py
+++ b/server/apps/sites/services/profile_services.py
@@ -3,9 +3,10 @@
from django.db import transaction
from apps.core.services import RcrainfoService
-from apps.sites.models import RcraProfile, RcraSitePermission, Site
+from apps.sites.models import RcraSitePermission, Site
from apps.sites.serializers import RcraPermissionSerializer
+from ...core.models import RcraProfile
from .site_services import RcraSiteService, SiteService
diff --git a/server/apps/sites/tests/conftest.py b/server/apps/sites/tests/conftest.py
index 2e2220195..d96e46a60 100644
--- a/server/apps/sites/tests/conftest.py
+++ b/server/apps/sites/tests/conftest.py
@@ -2,8 +2,8 @@
import pytest
+from apps.core.models import RcraProfile
from apps.sites.models import (
- RcraProfile,
RcraSitePermission,
Site,
)
diff --git a/server/apps/sites/tests/test_epa_profile_views.py b/server/apps/sites/tests/test_epa_profile_views.py
index cc23e99bb..f17f09b12 100644
--- a/server/apps/sites/tests/test_epa_profile_views.py
+++ b/server/apps/sites/tests/test_epa_profile_views.py
@@ -3,7 +3,7 @@
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
-from apps.sites.views import RcraProfileView
+from apps.core.views import RcraProfileView
class TestRcraProfileView:
diff --git a/server/apps/sites/tests/test_models.py b/server/apps/sites/tests/test_models.py
index fae97d97d..2a5d84782 100644
--- a/server/apps/sites/tests/test_models.py
+++ b/server/apps/sites/tests/test_models.py
@@ -2,7 +2,8 @@
from django.core.exceptions import ValidationError
from django.db import IntegrityError
-from apps.sites.models import Address, Contact, RcraProfile
+from apps.core.models import RcraProfile
+from apps.sites.models import Address, Contact
@pytest.mark.django_db
diff --git a/server/apps/sites/tests/test_serializers.py b/server/apps/sites/tests/test_serializers.py
index 1c8246b5e..43424921c 100644
--- a/server/apps/sites/tests/test_serializers.py
+++ b/server/apps/sites/tests/test_serializers.py
@@ -1,5 +1,3 @@
-import json
-
import pytest
from apps.sites.models import RcraSitePermission
diff --git a/server/apps/sites/tests/test_site_views.py b/server/apps/sites/tests/test_site_views.py
index 7905e17a4..76e8f5e85 100644
--- a/server/apps/sites/tests/test_site_views.py
+++ b/server/apps/sites/tests/test_site_views.py
@@ -6,7 +6,8 @@
from rest_framework.response import Response
from rest_framework.test import APIClient, APIRequestFactory, force_authenticate
-from apps.sites.models import RcraProfile, RcraSite, RcraSitePermission, Site
+from apps.core.models import RcraProfile
+from apps.sites.models import RcraSite, RcraSitePermission, Site
from apps.sites.views import SiteDetailView, SiteMtnListView
diff --git a/server/apps/sites/urls.py b/server/apps/sites/urls.py
index 324a63421..151cc8007 100644
--- a/server/apps/sites/urls.py
+++ b/server/apps/sites/urls.py
@@ -1,21 +1,14 @@
from django.urls import path
from apps.sites.views import (
- RcraProfileView,
- RcraSitePermissionView,
RcraSiteSearchView,
RcraSiteView,
SiteDetailView,
SiteListView,
SiteMtnListView,
- SyncProfileView,
)
urlpatterns = [
- # Rcra Profile
- path("profile//sync", SyncProfileView.as_view()),
- path("profile/", RcraProfileView.as_view()),
- path("site/permission/", RcraSitePermissionView.as_view()),
# Site
path("site/", SiteListView.as_view()),
path("site/", SiteDetailView.as_view()),
diff --git a/server/apps/sites/views/__init__.py b/server/apps/sites/views/__init__.py
index cf57ae5f0..ad5391971 100644
--- a/server/apps/sites/views/__init__.py
+++ b/server/apps/sites/views/__init__.py
@@ -1,9 +1,3 @@
-from .profile_views import (
- RcraPermissionView,
- RcraProfileView,
- RcraSitePermissionView,
- SyncProfileView,
-)
from .site_views import (
RcraSiteSearchView,
RcraSiteView,
diff --git a/server/apps/trak/migrations/0001_initial.py b/server/apps/trak/migrations/0001_initial.py
index 4f2bcca3e..ce6cc4d69 100644
--- a/server/apps/trak/migrations/0001_initial.py
+++ b/server/apps/trak/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.1.7 on 2023-05-06 13:55
+# Generated by Django 4.2.1 on 2023-06-12 18:32
import apps.trak.models.contact_models
import apps.trak.models.manifest_models
@@ -570,6 +570,62 @@ class Migration(migrations.Migration):
"ordering": ["sign_date"],
},
),
+ migrations.CreateModel(
+ name="CorrectionInfo",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ ("version_number", models.IntegerField(blank=True, null=True)),
+ ("active", models.BooleanField(blank=True, null=True)),
+ ("ppc_active", models.BooleanField(blank=True, null=True)),
+ ("epa_site_id", models.CharField(blank=True, max_length=100, null=True)),
+ (
+ "initiator_role",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("IN", "Industry"),
+ ("PP", "Ppc"),
+ ("EP", "Epa"),
+ ("ST", "State"),
+ ],
+ max_length=2,
+ null=True,
+ ),
+ ),
+ (
+ "update_role",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("IN", "Industry"),
+ ("PP", "Ppc"),
+ ("EP", "Epa"),
+ ("ST", "State"),
+ ],
+ max_length=2,
+ null=True,
+ ),
+ ),
+ (
+ "electronic_signature_info",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ to="trak.esignature",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "Correction Info",
+ "verbose_name_plural": "Correction Info",
+ },
+ ),
migrations.CreateModel(
name="Transporter",
fields=[
diff --git a/server/apps/trak/migrations/0002_correctioninfo.py b/server/apps/trak/migrations/0002_correctioninfo.py
deleted file mode 100644
index 3569fa6c0..000000000
--- a/server/apps/trak/migrations/0002_correctioninfo.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Generated by Django 4.1.7 on 2023-05-11 09:18
-
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ("trak", "0001_initial"),
- ]
-
- operations = [
- migrations.CreateModel(
- name="CorrectionInfo",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
- ),
- ),
- ("version_number", models.IntegerField(blank=True, null=True)),
- ("active", models.BooleanField(blank=True, null=True)),
- ("ppc_active", models.BooleanField(blank=True, null=True)),
- ("epa_site_id", models.CharField(blank=True, max_length=100, null=True)),
- (
- "initiator_role",
- models.CharField(
- blank=True,
- choices=[
- ("IN", "Industry"),
- ("PP", "Ppc"),
- ("EP", "Epa"),
- ("ST", "State"),
- ],
- max_length=2,
- null=True,
- ),
- ),
- (
- "update_role",
- models.CharField(
- blank=True,
- choices=[
- ("IN", "Industry"),
- ("PP", "Ppc"),
- ("EP", "Epa"),
- ("ST", "State"),
- ],
- max_length=2,
- null=True,
- ),
- ),
- (
- "electronic_signature_info",
- models.ForeignKey(
- blank=True,
- null=True,
- on_delete=django.db.models.deletion.PROTECT,
- to="trak.esignature",
- ),
- ),
- ],
- options={
- "verbose_name": "Correction Info",
- "verbose_name_plural": "Correction Info",
- },
- ),
- ]
diff --git a/server/apps/trak/signals.py b/server/apps/trak/signals.py
index 8ddb949ef..3e481162e 100644
--- a/server/apps/trak/signals.py
+++ b/server/apps/trak/signals.py
@@ -2,7 +2,7 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
-from apps.sites.models.profile_models import RcraProfile
+from apps.core.models import RcraProfile
@receiver(post_save, sender=User)
diff --git a/server/fixtures/dev_data.yaml b/server/fixtures/dev_data.yaml
index 76d019e86..d260b6a27 100644
--- a/server/fixtures/dev_data.yaml
+++ b/server/fixtures/dev_data.yaml
@@ -20,9 +20,9 @@
last_login: null
is_superuser: false
username: testuser1
- first_name: ''
- last_name: ''
- email: ''
+ first_name: 'David'
+ last_name: 'Graham'
+ email: 'myemail@proton.me'
is_staff: false
is_active: true
date_joined: 2022-12-17 19:17:58.260000+00:00
@@ -669,7 +669,7 @@
can_esign: true
limited_esign: true
registered_emanifest_user: true
-- model: sites.rcraprofile
+- model: core.rcraprofile
pk: 1
fields:
user: 2
diff --git a/server/haztrak/settings.py b/server/haztrak/settings.py
index d8b00d77d..ed5e1cd4c 100644
--- a/server/haztrak/settings.py
+++ b/server/haztrak/settings.py
@@ -163,8 +163,7 @@
"DESCRIPTION": "An open-source web app illustrating how hazardous waste "
"management software can integrate with EPA's RCRAInfo",
"VERSION": HAZTRAK_VERSION,
- "SERVE_INCLUDE_SCHEMA": True,
- # "SCHEMA_PATH_PREFIX": r"/api/[a-zA-Z]*/", # ToDo: redesign endpoints
+ "SERVE_INCLUDE_SCHEMA": False,
"SWAGGER_UI_SETTINGS": {
"deepLinking": True,
"persistAuthorization": True,