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

Feature/registration #671

Merged
merged 33 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
524fc83
add phonenumber field
Sep 13, 2023
f7bb910
first draft of email and phone number backend
Sep 14, 2023
9090bc7
finalize adding phone number and mail to user
Sep 14, 2023
e473bf4
fix eslint errors
Sep 14, 2023
3bb6cb5
fix yapf
Sep 21, 2023
31b9170
add email migration
Sep 21, 2023
fa831ca
Merge branch 'master' into feature/registration
magsyg Sep 21, 2023
1fd8138
fix tests and pytz
Sep 21, 2023
83cefd6
Merge branch 'master' into feature/registration
magsyg Sep 21, 2023
9155252
fix tests and pytz
Sep 21, 2023
5f716bc
fix merge
Sep 21, 2023
1827644
Merge branch 'master' into feature/registration
magsyg Sep 26, 2023
7880f92
fix migrations with master
Sep 28, 2023
317f8ee
fix regex and remove pytz
Sep 28, 2023
04506d0
add constant
Nov 2, 2023
cf92828
fix merge
Nov 2, 2023
a96316e
lol
Nov 2, 2023
92d2211
clean migrations
Nov 2, 2023
e4952b3
Merge branch 'master' into feature/registration
magsyg Nov 16, 2023
f54dd1f
fix const name for phonenumber
Nov 16, 2023
4330966
fix yapf for files i just merged
Nov 16, 2023
2afade9
small fixes
Dec 16, 2023
ac928b0
fix merge
Dec 16, 2023
28b6845
fix pipfilelock
Dec 16, 2023
ed59384
fix migrations
Dec 17, 2023
f026fd4
add migrations
Dec 17, 2023
bac1f9e
fix bandit
Dec 17, 2023
1b672f4
remove blacklisted bandit method
Dec 17, 2023
8039984
fix
Dec 17, 2023
326f3d5
Delete Pipfile
magsyg Dec 17, 2023
83b0355
Merge branch 'master' into feature/registration
magsyg Dec 17, 2023
1484dc3
Merge branch 'master' into feature/registration
magsyg Dec 19, 2023
df0cf9e
final commit for email and phonenumber
Dec 19, 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
1 change: 1 addition & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ debugpy = "*"
gunicorn = "*"
django-admin-autocomplete-filter = "*"
django-notifications-hq = "*"
pytz = "*"
emilte marked this conversation as resolved.
Show resolved Hide resolved

[dev-packages]
yapf = "*"
Expand Down
511 changes: 261 additions & 250 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 4.2.3 on 2023-09-14 16:35

from django.db import migrations, models
import samfundet.models.utils.fields


class Migration(migrations.Migration):

dependencies = [
('samfundet', '0036_venue_slug'),
]

operations = [
migrations.AddField(
model_name='user',
name='phone_number',
field=samfundet.models.utils.fields.PhoneNumberField(default=59696969, max_length=15, verbose_name='phone_number'),
preserve_default=False,
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(error_messages={'unique': 'A user with that email already exists.'}, max_length=254, unique=True, verbose_name='email'),
),
]
18 changes: 18 additions & 0 deletions backend/samfundet/migrations/0038_alter_user_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.3 on 2023-09-21 17:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('samfundet', '0037_user_phone_number_alter_user_email'),
]

operations = [
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='email'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Generated by Django 4.2.5 on 2023-09-21 17:40

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import jsonfield.fields


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('samfundet', '0038_alter_user_email'),
]

operations = [
migrations.AlterModelOptions(
name='notification',
options={'ordering': ('-timestamp',), 'verbose_name': 'Notification', 'verbose_name_plural': 'Notifications'},
),
migrations.AlterField(
model_name='notification',
name='action_object_content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_action_object', to='contenttypes.contenttype', verbose_name='action object content type'),
),
migrations.AlterField(
model_name='notification',
name='action_object_object_id',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='action object object id'),
),
migrations.AlterField(
model_name='notification',
name='actor_content_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notify_actor', to='contenttypes.contenttype', verbose_name='actor content type'),
),
migrations.AlterField(
model_name='notification',
name='actor_object_id',
field=models.CharField(max_length=255, verbose_name='actor object id'),
),
migrations.AlterField(
model_name='notification',
name='data',
field=jsonfield.fields.JSONField(blank=True, null=True, verbose_name='data'),
),
migrations.AlterField(
model_name='notification',
name='deleted',
field=models.BooleanField(db_index=True, default=False, verbose_name='deleted'),
),
migrations.AlterField(
model_name='notification',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='description'),
),
migrations.AlterField(
model_name='notification',
name='emailed',
field=models.BooleanField(db_index=True, default=False, verbose_name='emailed'),
),
migrations.AlterField(
model_name='notification',
name='level',
field=models.CharField(choices=[('success', 'success'), ('info', 'info'), ('warning', 'warning'), ('error', 'error')], default='info', max_length=20, verbose_name='level'),
),
migrations.AlterField(
model_name='notification',
name='public',
field=models.BooleanField(db_index=True, default=True, verbose_name='public'),
),
migrations.AlterField(
model_name='notification',
name='recipient',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL, verbose_name='recipient'),
),
migrations.AlterField(
model_name='notification',
name='target_content_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notify_target', to='contenttypes.contenttype', verbose_name='target content type'),
),
migrations.AlterField(
model_name='notification',
name='target_object_id',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='target object id'),
),
migrations.AlterField(
model_name='notification',
name='timestamp',
field=models.DateTimeField(db_index=True, default=django.utils.timezone.now, verbose_name='timestamp'),
),
migrations.AlterField(
model_name='notification',
name='unread',
field=models.BooleanField(db_index=True, default=True, verbose_name='unread'),
),
migrations.AlterField(
model_name='notification',
name='verb',
field=models.CharField(max_length=255, verbose_name='verb'),
),
]
14 changes: 13 additions & 1 deletion backend/samfundet/models/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from root.utils import permissions

from .utils.fields import LowerCaseField
from .utils.fields import LowerCaseField, PhoneNumberField

if TYPE_CHECKING:
from typing import Any, Optional
Expand Down Expand Up @@ -95,6 +95,18 @@ class User(AbstractUser):
'unique': _('A user with that username already exists.'),
},
)
phone_number = PhoneNumberField(
_('phone_number'),
blank=False,
null=False,
editable=True,
)
email = models.EmailField(
_('email'),
magsyg marked this conversation as resolved.
Show resolved Hide resolved
blank=False,
null=False,
unique=True,
)

class Meta:
permissions = [
Expand Down
14 changes: 14 additions & 0 deletions backend/samfundet/models/utils/fields.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
from django.db import models
from django.core.validators import RegexValidator


class LowerCaseField(models.CharField):

def to_python(self, value: str) -> str:
return super().to_python(value.lower())


class PhoneNumberField(models.CharField):

def __init__(self, *args, **kwargs) -> None:
kwargs['max_length'] = 15
self.validators = [
RegexValidator(
regex=r'^([\+]?[(]?[0-9]{3}[)]?[-\s\.]?)?[0-9]{3}[-\s\.]?[0-9]{4,6}$',
message='Enter a valid phonenumber',
),
]
super().__init__(*args, **kwargs)
16 changes: 14 additions & 2 deletions backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,20 @@ def validate(self, attrs: dict) -> dict:
class RegisterSerializer(serializers.Serializer):
"""
This serializer defines two fields for authentication:
magsyg marked this conversation as resolved.
Show resolved Hide resolved
* username
* email
* phone_number
* firstname
* lastname
* password
"""
username = serializers.EmailField(label='Username', write_only=True)
username = serializers.CharField(label='Username', write_only=True)
email = serializers.EmailField(label='Email', write_only=True)
phone_number = serializers.RegexField(
label='Phonenumber',
regex=r'^([\+]?[(]?[0-9]{3}[)]?[-\s\.]?)?[0-9]{3}[-\s\.]?[0-9]{4,6}$',
write_only=True,
)
firstname = serializers.CharField(label='First name', write_only=True)
lastname = serializers.CharField(label='Last name', write_only=True)
password = serializers.CharField(
Expand All @@ -266,13 +274,17 @@ def validate(self, attrs: dict) -> dict:
# Inherited function.
# Take username and password from request.
username = attrs.get('username')
email = attrs.get('email')
phone_number = attrs.get('phone_number')
firstname = attrs.get('firstname')
lastname = attrs.get('lastname')
password = attrs.get('password')

if username and password:
# Try to authenticate the user using Django auth framework.
user = User.objects.create_user(first_name=firstname, last_name=lastname, username=username, password=password)
user = User.objects.create_user(
first_name=firstname, last_name=lastname, username=username, email=email, phone_number=phone_number, password=password
)
user = authenticate(request=self.context.get('request'), username=username, password=password)
else:
msg = 'Both "username" and "password" are required.'
Expand Down
10 changes: 8 additions & 2 deletions backend/samfundet/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,14 @@ def test_staff_permission_admin_panel(fixture_django_client: Client, fixture_sta
def test_staff_object_permission_admin_panel(fixture_django_client: Client, fixture_staff: User):
fixture_django_client.force_login(user=fixture_staff)

some_user = User.objects.create_user(username='some_user')
other_user = User.objects.create_user(username='other_user')
some_user = User.objects.create_user(
username='some_user',
email='[email protected]',
)
other_user = User.objects.create_user(
username='other_user',
email='[email protected]',
)

url_some_user = reverse(routes.admin__samfundet_user_change, args=[some_user.id])
url_other_user = reverse(routes.admin__samfundet_user_change, args=[other_user.id])
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/Components/Footer/Footer.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default {
component: Footer,
} as ComponentMeta<typeof Footer>;

const Template: ComponentStory<typeof Footer> = function (args) {
const Template: ComponentStory<typeof Footer> = function () {
return <Footer />;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Navigate, useLocation } from 'react-router-dom';
import { useAuthContext } from '~/AuthContext';
import { hasPerm } from '~/utils';
import { ROUTES } from '~/routes';
import { PERM } from '~/permissions';
import { ElementType } from 'react';

type ProtectedRouteProps = {
Expand Down
1 change: 0 additions & 1 deletion frontend/src/Pages/LoginPage/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { Alert, Page } from '~/Components';
import { SamfForm } from '~/Forms/SamfForm';
import { SamfFormField } from '~/Forms/SamfFormField';
import { getUser, login } from '~/api';

import { STATUS } from '~/http_status_codes';
import { KEY } from '~/i18n/constants';
import { ROUTES } from '~/routes';
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/Pages/SignUpPage/SignUpPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ export function SignUpPage() {
}, [user, navigate]);

function handleRegistration(formData: Record<string, string>) {
register(formData['username'], formData['firstname'], formData['lastname'], formData['password'])
register(
formData['username'],
formData['email'],
formData['phone_number'],
formData['firstname'],
formData['lastname'],
formData['password'],
magsyg marked this conversation as resolved.
Show resolved Hide resolved
)
.then((status) => {
if (status === STATUS.HTTP_202_ACCEPTED) {
getUser().then((user) => {
Expand All @@ -49,7 +56,7 @@ export function SignUpPage() {
<div className={styles.login_container}>
{loginFailed && (
<Alert
message="Login failed"
message="Register failed"
type="error"
align="center"
closable={true}
Expand All @@ -67,6 +74,8 @@ export function SignUpPage() {
type="text"
label={t(KEY.loginpage_email_placeholder) ?? ''}
/>
<SamfFormField required={true} field="email" type="email" label={t(KEY.common_email) ?? ''} />
<SamfFormField required={true} field="phone_number" type="text" label={t(KEY.common_phonenumber) ?? ''} />
<SamfFormField required={true} field="firstname" type="text" label={t(KEY.common_firstname) ?? ''} />
<SamfFormField required={true} field="lastname" type="text" label={t(KEY.common_lastname) ?? ''} />
<SamfFormField required={true} field="password" type="password" label={t(KEY.common_password) ?? ''} />
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ export async function logout(): Promise<AxiosResponse> {

export async function register(
username: string,
email: string,
phone_number: string,
firstname: string,
lastname: string,
password: string,
): Promise<number> {
const url = BACKEND_DOMAIN + ROUTES.backend.samfundet__register;
const data = { username, firstname, lastname, password };
const data = { username, email, phone_number, firstname, lastname, password };
const response = await axios.post(url, data, { withCredentials: true });

// Django rotates csrftoken after login, set new token received.
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ export const KEY = {
common_username: 'common_username',
common_lastname: 'common_lastname',
common_register: 'common_register',
common_email: 'common_email',
common_phonenumber: 'common_phonenumber',
common_password: 'common_password',
common_about_us: 'common_about_us',
common_overview: 'common_overview',
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export const nb: Record<KeyValues, string> = {
[KEY.common_whatsup]: 'Hva skjer?',
[KEY.common_sponsor]: 'Sponsorer',
[KEY.common_lastname]: 'Etternavn',
[KEY.common_email]: 'Email',
[KEY.common_phonenumber]: 'Phonenumber',
[KEY.common_register]: 'Registrer',
[KEY.common_password]: 'passord',
[KEY.common_about_us]: 'Om oss',
Expand Down Expand Up @@ -115,7 +117,7 @@ export const nb: Record<KeyValues, string> = {
// LoginPage:
[KEY.loginpage_register]: 'Lag bruker',
[KEY.loginpage_internal_login]: 'Logg inn som intern',
[KEY.loginpage_email_placeholder]: 'E-post eller medlemsnummer',
[KEY.loginpage_email_placeholder]: 'Brukernavn',
magsyg marked this conversation as resolved.
Show resolved Hide resolved
[KEY.loginpage_forgotten_password]: 'Glemt passordet ditt?',

// GroupsPage:
Expand Down Expand Up @@ -293,6 +295,8 @@ export const en: Record<KeyValues, string> = {
[KEY.common_english]: 'English',
[KEY.common_contact]: 'Contact',
[KEY.common_register]: 'Register',
[KEY.common_email]: 'Email',
[KEY.common_phonenumber]: 'Telefonnummer',
[KEY.common_lastname]: 'Last name',
[KEY.common_password]: 'password',
[KEY.common_overview]: 'Oversikt',
Expand Down Expand Up @@ -338,7 +342,7 @@ export const en: Record<KeyValues, string> = {
// LoginPage:
[KEY.loginpage_register]: 'Create user',
[KEY.loginpage_internal_login]: 'Log in as internal',
[KEY.loginpage_email_placeholder]: 'Email or membership ID',
[KEY.loginpage_email_placeholder]: 'Username',
magsyg marked this conversation as resolved.
Show resolved Hide resolved
[KEY.loginpage_forgotten_password]: 'Forgot password?',

// GroupsPage:
Expand Down