Skip to content

Commit

Permalink
Merge branch 'master' into 428-create-seed-script-for-textitems
Browse files Browse the repository at this point in the history
  • Loading branch information
sindrelothe committed Sep 20, 2023
2 parents 848cdb9 + cb8212f commit e55dc8a
Show file tree
Hide file tree
Showing 39 changed files with 924 additions and 89 deletions.
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,28 @@
- **[Technical Documentation](/docs/technical/README.md)**
- [Work Methodology](/docs/work-methodology.md)
- [Useful Commands](/docs/useful-commands.md)

- [Technologies used on Samf4 🤖](/docs/technical/Samf4Tech.md)
- [Useful Docker aliases](/docs/docker-project-specific-commands.md)
## Installation

We have a script that handles all installation for you. To run the script, a Github Personal Access Token (PAT) is required.
You can make one here https://github.com/settings/tokens/new. Tick scopes `repo`, `read:org` and `admin:public_key`),
We have a script that handles all installation for you. To run the script, a Github Personal Access Token (PAT) is required.
You can make one here https://github.com/settings/tokens/new. Tick scopes `repo`, `read:org` and `admin:public_key`),
then store the token somewhere safe (Github will never show it again).

Copy these commands (press button on the right-hand side of the block)
Copy these commands (press button on the right-hand side of the block)
and run from the directory you would clone the project.

```sh
# non-interactive
read -s -p "Github PAT token: " TOKEN ; X_INTERACTIVE=n /bin/bash -c "$(curl -fsSL https://$TOKEN@raw.githubusercontent.com/Samfundet/Samfundet4/master/{bash_utils.sh,install.sh})" && . ~/.bash_profile && cd Samfundet4; unset TOKEN; unset X_INTERACTIVE;
# Interactive
read -s -p "Github PAT token: " TOKEN ; X_INTERACTIVE=y /bin/bash -c "$(curl -fsSL https://$TOKEN@raw.githubusercontent.com/Samfundet/Samfundet4/master/{bash_utils.sh,install.sh})" && . ~/.bash_profile && cd Samfundet4; unset TOKEN; unset X_INTERACTIVE;
```

<details>
<summary>Interactive (show/hide)</summary>
<summary>Non-interactive (show/hide)</summary>

```sh
# interactive
read -s -p "Github PAT token: " TOKEN ; X_INTERACTIVE=y /bin/bash -c "$(curl -fsSL https://$TOKEN@raw.githubusercontent.com/Samfundet/Samfundet4/master/{bash_utils.sh,install.sh})" && . ~/.bash_profile && cd Samfundet4; unset TOKEN; unset X_INTERACTIVE;
# Non-interactive
read -s -p "Github PAT token: " TOKEN ; X_INTERACTIVE=n /bin/bash -c "$(curl -fsSL https://$TOKEN@raw.githubusercontent.com/Samfundet/Samfundet4/master/{bash_utils.sh,install.sh})" && . ~/.bash_profile && cd Samfundet4; unset TOKEN; unset X_INTERACTIVE;
```

<!--
Expand All @@ -38,12 +39,12 @@ cd ~/my-projects/test; rm -rf Samfundet4; read -s -p "Github PAT token: " TOKEN
<details>
<summary>Flags explained (show/hide)</summary>

> - X_INTERACTIVE (y/n): determines how many prompts you receive before performing an action.
> curl:
> - -f: fail fast
> - -s: silent, no progress-meter
> - -S: show error on fail
> - -L: follow redirect
> - X_INTERACTIVE (y/n): determines how many prompts you receive before performing an action.
> curl:
> - -f: fail fast
> - -s: silent, no progress-meter
> - -S: show error on fail
> - -L: follow redirect
</details>

Expand Down
2 changes: 1 addition & 1 deletion backend/.docker.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ENABLE_DEBUGPY=yes
# Automatically create local superuser
DJANGO_SUPERUSER_USERNAME=emilte
DJANGO_SUPERUSER_PASSWORD=Django123
DJANGO_SUPERUSER_EMAIL=[email protected]
DJANGO_SUPERUSER_EMAIL=[email protected]

DOMAIN=0.0.0.0

Expand Down
22 changes: 22 additions & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[scripts]
# See '/docs/pipenv.md'.
"pipenv:install" = "pipenv install"
"pipenv:update" = "pipenv update"
"pipenv:sync" = "bash -c \"pipenv clean; pipenv sync --dev\""
"pipenv:docker-install-dev" = "pipenv install --deploy --ignore-pipfile --dev" # 'deploy' means abort if outdated lock file. 'ignore-pipfile' means only install using the lock file. 'dev' means install dev dependencies.
"pipenv:docker-install-prod" = "pipenv install --deploy --ignore-pipfile" # 'deploy' means abort if outdated lock file. 'ignore-pipfile' means only install using the lock file.
"pipenv:outdated" = "pipenv update --outdated" # Show outdated dependencies.
"pipenv:graph" = "pipenv graph" # Show dependency graph.
"pipenv:where" = "pipenv --where" # Show location of virtual environment.
"pipenv:rm" = "pipenv --rm" # Completely remove virtual environment.
"pipenv:shell" = "pipenv shell" # Opens a shell within the virtual environment.
"mypy:run" = "pipenv run mypy --config-file mypy.ini ."
"migrations:verify" = "pipenv run python manage.py makemigrations --check --dry-run --noinput --verbosity 2"
"bandit:run" = "pipenv run bandit --recursive --ini .bandit ."
"flake8:run" = "pipenv run flake8 --config=.flake8 ."
"yapf:diff" = "pipenv run yapf --parallel --recursive --diff ." # Dry-run yapf on all files in the project.
"yapf:apply" = "pipenv run yapf --parallel --recursive -i ." # Applies yapf to all files in the project.
"pytest:run" = "pipenv run pytest"
"pipeline:run" = "bash -c \"pipenv run mypy:run && pipenv run yapf:diff && pipenv run migrations:verify && pipenv run bandit:run && pipenv run flake8:run && pipenv run pytest:run\""
"seed:run" = "pipenv run python manage.py seed"

[packages]
django = "*"
pytest = "*"
Expand Down
2 changes: 2 additions & 0 deletions backend/root/management/commands/seed_scripts/venues.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import random

from django.utils import timezone
from django.utils.text import slugify

from root.utils.samfundet_random import words
from samfundet.models.general import Venue
Expand All @@ -26,6 +27,7 @@ def seed():
for i, name in enumerate(VENUES):
Venue.objects.create(
name=name,
slug=slugify(name),
description=words(10),
floor=random.randint(1, 4),
last_renovated=timezone.now() + timezone.timedelta(days=-random.randint(30, 365 * 30)),
Expand Down
23 changes: 22 additions & 1 deletion backend/root/utils/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
DO NOT WRITE IN THIS FILE, AS IT WILL BE OVERWRITTEN ON NEXT UPDATE.
THIS FILE WAS GENERATED BY: root.management.commands.generate_routes
LAST UPDATE: 2023-08-17 17:48:38.961443+00:00
LAST UPDATE: 2023-09-04 15:22:16.450169+00:00
"""

############################################################
Expand Down Expand Up @@ -308,18 +308,36 @@
admin__samfundet_keyvalue_delete = 'admin:samfundet_keyvalue_delete'
admin__samfundet_keyvalue_change = 'admin:samfundet_keyvalue_change'
adminsamfundetkeyvalue__objectId = ''
admin__samfundet_recruitment_permissions = 'admin:samfundet_recruitment_permissions'
admin__samfundet_recruitment_permissions_manage_user = 'admin:samfundet_recruitment_permissions_manage_user'
admin__samfundet_recruitment_permissions_manage_group = 'admin:samfundet_recruitment_permissions_manage_group'
admin__samfundet_recruitment_changelist = 'admin:samfundet_recruitment_changelist'
admin__samfundet_recruitment_add = 'admin:samfundet_recruitment_add'
admin__samfundet_recruitment_history = 'admin:samfundet_recruitment_history'
admin__samfundet_recruitment_delete = 'admin:samfundet_recruitment_delete'
admin__samfundet_recruitment_change = 'admin:samfundet_recruitment_change'
adminsamfundetrecruitment__objectId = ''
admin__samfundet_recruitmentposition_permissions = 'admin:samfundet_recruitmentposition_permissions'
admin__samfundet_recruitmentposition_permissions_manage_user = 'admin:samfundet_recruitmentposition_permissions_manage_user'
admin__samfundet_recruitmentposition_permissions_manage_group = 'admin:samfundet_recruitmentposition_permissions_manage_group'
admin__samfundet_recruitmentposition_changelist = 'admin:samfundet_recruitmentposition_changelist'
admin__samfundet_recruitmentposition_add = 'admin:samfundet_recruitmentposition_add'
admin__samfundet_recruitmentposition_history = 'admin:samfundet_recruitmentposition_history'
admin__samfundet_recruitmentposition_delete = 'admin:samfundet_recruitmentposition_delete'
admin__samfundet_recruitmentposition_change = 'admin:samfundet_recruitmentposition_change'
adminsamfundetrecruitmentposition__objectId = ''
admin__samfundet_recruitmentadmission_permissions = 'admin:samfundet_recruitmentadmission_permissions'
admin__samfundet_recruitmentadmission_permissions_manage_user = 'admin:samfundet_recruitmentadmission_permissions_manage_user'
admin__samfundet_recruitmentadmission_permissions_manage_group = 'admin:samfundet_recruitmentadmission_permissions_manage_group'
admin__samfundet_recruitmentadmission_changelist = 'admin:samfundet_recruitmentadmission_changelist'
admin__samfundet_recruitmentadmission_add = 'admin:samfundet_recruitmentadmission_add'
admin__samfundet_recruitmentadmission_history = 'admin:samfundet_recruitmentadmission_history'
admin__samfundet_recruitmentadmission_delete = 'admin:samfundet_recruitmentadmission_delete'
admin__samfundet_recruitmentadmission_change = 'admin:samfundet_recruitmentadmission_change'
adminsamfundetrecruitmentadmission__objectId = ''
admin__samfundet_organization_permissions = 'admin:samfundet_organization_permissions'
admin__samfundet_organization_permissions_manage_user = 'admin:samfundet_organization_permissions_manage_user'
admin__samfundet_organization_permissions_manage_group = 'admin:samfundet_organization_permissions_manage_group'
admin__samfundet_organization_changelist = 'admin:samfundet_organization_changelist'
admin__samfundet_organization_add = 'admin:samfundet_organization_add'
admin__samfundet_organization_history = 'admin:samfundet_organization_history'
Expand Down Expand Up @@ -386,6 +404,8 @@
samfundet__table_detail = 'samfundet:table-detail'
samfundet__text_item_list = 'samfundet:text_item-list'
samfundet__text_item_detail = 'samfundet:text_item-detail'
samfundet__infobox_list = 'samfundet:infobox-list'
samfundet__infobox_detail = 'samfundet:infobox-detail'
samfundet__key_value_list = 'samfundet:key_value-list'
samfundet__key_value_detail = 'samfundet:key_value-detail'
samfundet__organizations_list = 'samfundet:organizations-list'
Expand Down Expand Up @@ -413,5 +433,6 @@
samfundet__assign_group = 'samfundet:assign_group'
samfundet__recruitment_positions = 'samfundet:recruitment_positions'
samfundet__active_recruitment_positions = 'samfundet:active_recruitment_positions'
samfundet__applicants_without_interviews = 'samfundet:applicants_without_interviews/'
static__path = ''
media__path = ''
30 changes: 29 additions & 1 deletion backend/samfundet/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from django.urls import reverse
from django.contrib import admin
from django.utils.html import format_html
from django.contrib.admin.models import LogEntry
from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
from guardian import models as guardian_models
from root.utils.routes import admin__samfundet_recruitmentadmission_change

from root.custom_classes.admin_classes import (
CustomGuardedUserAdmin,
Expand Down Expand Up @@ -488,6 +491,25 @@ class RecruitmentAdmin(CustomGuardedModelAdmin):
list_select_related = True


class RecruitmentAdmissionInline(admin.TabularInline):
"""
Inline admin interface for RecruitmentAdmission.
Displays a link to the detailed admin page of each admission along with its user and applicant priority.
"""
model = RecruitmentAdmission
extra = 0
readonly_fields = ['linked_admission_text', 'user', 'applicant_priority']
fields = ['linked_admission_text', 'user', 'applicant_priority']

def linked_admission_text(self, obj: RecruitmentAdmission) -> str:
"""
Returns a clickable link leading to the admin change page of the RecruitmentAdmission instance.
"""
url = reverse(admin__samfundet_recruitmentadmission_change, args=[obj.pk])
return format_html('<a href="{url}">{obj}</a>', url=url, obj=obj.admission_text)


@admin.register(RecruitmentPosition)
class RecruitmentPositionAdmin(CustomGuardedModelAdmin):
sortable_by = [
Expand All @@ -496,11 +518,17 @@ class RecruitmentPositionAdmin(CustomGuardedModelAdmin):
'gang',
'id',
]
list_display = ['name_nb', 'is_funksjonaer_position', 'gang', 'id']
list_display = ['name_nb', 'is_funksjonaer_position', 'gang', 'id', 'admissions_count']
search_fields = ['name_nb', 'is_funksjonaer_position', 'gang', 'id']
filter_horizontal = ['interviewers']
list_select_related = True

inlines = [RecruitmentAdmissionInline]

def admissions_count(self, obj: RecruitmentPosition) -> int:
count = obj.admissions.all().count()
return count


@admin.register(RecruitmentAdmission)
class RecruitmentAdmissionAdmin(CustomGuardedModelAdmin):
Expand Down
18 changes: 18 additions & 0 deletions backend/samfundet/migrations/0036_venue_slug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.3 on 2023-09-07 19:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('samfundet', '0035_recruitmentadmission'),
]

operations = [
migrations.AddField(
model_name='venue',
name='slug',
field=models.SlugField(null=True, unique=True),
),
]
1 change: 1 addition & 0 deletions backend/samfundet/models/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ def save(self, *args: Any, **kwargs: Any) -> None:

class Venue(models.Model):
name = models.CharField(max_length=140, blank=True, null=True, unique=True)
slug = models.SlugField(unique=True, null=True)
description = models.TextField(blank=True, null=True)
floor = models.IntegerField(blank=True, null=True)
last_renovated = models.DateTimeField(blank=True, null=True)
Expand Down
47 changes: 45 additions & 2 deletions backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,25 @@ class Meta:
fields = '__all__'


class UserForRecruitmentSerializer(serializers.ModelSerializer):
recruitment_admission_ids = serializers.SerializerMethodField()

class Meta:
model = User
fields = [
'id',
'first_name',
'last_name',
'username',
'email',
'recruitment_admission_ids', # Add this to the fields list
]

def get_recruitment_admission_ids(self, obj: User) -> list[int]:
"""Return list of recruitment admission IDs for the user."""
return RecruitmentAdmission.objects.filter(user=obj).values_list('id', flat=True)


class RecruitmentPositionSerializer(serializers.ModelSerializer):

class Meta:
Expand All @@ -506,14 +525,38 @@ class Meta:
fields = [
'admission_text',
'recruitment_position',
'user',
'applicant_priority',
'interview_time',
'interview_location',
]

def create(self, validated_data: dict) -> RecruitmentAdmission:
recruitment_position = validated_data['recruitment_position']
recruitment = recruitment_position.recruitment
user = self.context['request'].user
applicant_priority = 1

recruitment_admission = RecruitmentAdmission.objects.create(
admission_text=validated_data.get('admission_text'),
recruitment_position=recruitment_position,
recruitment=recruitment,
user=user,
applicant_priority=applicant_priority,
interview_time=validated_data.get('interview_time'),
interview_location=validated_data.get('interview_location')
)

return recruitment_admission


class ApplicantInfoSerializer(serializers.ModelSerializer):

class Meta:
model = User
fields = ['id', 'first_name', 'last_name', 'email']


class RecruitmentAdmissionForGangSerializer(serializers.ModelSerializer):
user = ApplicantInfoSerializer(read_only=True)

class Meta:
model = RecruitmentAdmission
Expand Down
1 change: 1 addition & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@
########## Recruitment ##########
path('recruitment-positions/', views.RecruitmentPositionsPerRecruitmentView.as_view(), name='recruitment_positions'),
path('active-recruitment-positions/', views.ActiveRecruitmentPositionsView.as_view(), name='active_recruitment_positions'),
path('applicants-without-interviews/', views.ApplicantsWithoutInterviewsView.as_view(), name='applicants_without_interviews/'),
]
Loading

0 comments on commit e55dc8a

Please sign in to comment.