fnschool 20251021.80058.842__py3-none-any.whl → 20251027.81653.841__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fnschool might be problematic. Click here for more details.
- fnschoo1/__init__.py +1 -1
- fnschoo1/canteen/forms.py +2 -2
- fnschoo1/canteen/migrations/0019_category_updated_at_ingredient_created_at_and_more.py +48 -0
- fnschoo1/canteen/migrations/0020_alter_ingredient_created_at.py +20 -0
- fnschoo1/canteen/models.py +23 -3
- fnschoo1/canteen/templates/canteen/category/list.html +1 -1
- fnschoo1/canteen/templates/canteen/consumption/create.html +6 -1
- fnschoo1/canteen/templates/canteen/ingredient/list.html +11 -2
- fnschoo1/canteen/templates/canteen/meal_type/list.html +1 -1
- fnschoo1/fnprofile/__init__.py +0 -0
- fnschoo1/fnprofile/admin.py +27 -0
- fnschoo1/fnprofile/apps.py +12 -0
- fnschoo1/fnprofile/forms.py +73 -0
- fnschoo1/fnprofile/migrations/0001_initial.py +213 -0
- fnschoo1/fnprofile/migrations/0002_auto_20251026_2235.py +42 -0
- fnschoo1/fnprofile/migrations/0003_alter_fnuser_options.py +20 -0
- fnschoo1/fnprofile/migrations/__init__.py +0 -0
- fnschoo1/fnprofile/models.py +90 -0
- fnschoo1/fnprofile/signals.py +20 -0
- fnschoo1/fnprofile/templates/fnprofile/create.html +18 -0
- fnschoo1/fnprofile/templates/fnprofile/detail.html +16 -0
- fnschoo1/fnprofile/templates/fnprofile/edit.html +16 -0
- fnschoo1/fnprofile/templates/fnprofile/log_in.html +22 -0
- fnschoo1/fnprofile/templates/fnprofile/log_out.html +12 -0
- fnschoo1/fnprofile/tests.py +3 -0
- fnschoo1/fnprofile/urls.py +15 -0
- fnschoo1/fnprofile/views.py +69 -0
- fnschoo1/fnschool/settings.py +4 -2
- fnschoo1/fnschool/settings_fn_profile_migration_0001.py +180 -0
- fnschoo1/fnschool/urls.py +7 -2
- fnschoo1/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- fnschoo1/profiles/__init__.py +7 -0
- fnschoo1/profiles/forms.py +6 -0
- fnschoo1/profiles/migrations/0006_profile_created_at_profile_updated_at.py +27 -0
- fnschoo1/profiles/migrations/0007_alter_profile_created_at.py +20 -0
- fnschoo1/profiles/migrations/0008_alter_profile_groups_alter_profile_user_permissions.py +38 -0
- fnschoo1/profiles/models.py +32 -2
- fnschoo1/profiles/templates/profiles/create.html +1 -1
- fnschoo1/profiles/templates/profiles/detail.html +1 -1
- fnschoo1/profiles/templates/profiles/edit.html +4 -2
- fnschoo1/profiles/templates/profiles/log_in.html +1 -1
- fnschoo1/profiles/templates/profiles/log_out.html +1 -1
- fnschoo1/profiles/views.py +7 -1
- fnschoo1/static/css/fnschool.css +14 -0
- fnschoo1/static/js/fnschool.js +20 -0
- fnschoo1/templates/home.html +1 -1
- fnschoo1/templates/includes/_header.html +30 -6
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/METADATA +1 -1
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/RECORD +59 -35
- /fnschoo1/templates/base/{header_content_footer.html → document.html} +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/SOURCES.txt.py +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/WHEEL +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/dependency_links.txt.py +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/entry_points.txt +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/entry_points.txt.py +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/licenses/LICENSE +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/requires.txt.py +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/top_level.txt +0 -0
- {fnschool-20251021.80058.842.dist-info → fnschool-20251027.81653.841.dist-info}/top_level.txt.py +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from django.contrib.auth.models import (
|
|
2
|
+
AbstractBaseUser,
|
|
3
|
+
AbstractUser,
|
|
4
|
+
BaseUserManager,
|
|
5
|
+
PermissionsMixin,
|
|
6
|
+
User,
|
|
7
|
+
)
|
|
8
|
+
from django.db import models
|
|
9
|
+
from django.db.models.signals import post_save
|
|
10
|
+
from django.dispatch import receiver
|
|
11
|
+
from django.utils.translation import gettext as _
|
|
12
|
+
from fnschool import *
|
|
13
|
+
|
|
14
|
+
# Create your models here.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Gender(models.TextChoices):
|
|
18
|
+
MALE = "M", _("Male")
|
|
19
|
+
FEMALE = "F", _("Female")
|
|
20
|
+
UNKNOWN = "U", "--"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Fnuser(AbstractUser, PermissionsMixin):
|
|
24
|
+
groups = models.ManyToManyField(
|
|
25
|
+
"auth.Group",
|
|
26
|
+
verbose_name="groups",
|
|
27
|
+
blank=True,
|
|
28
|
+
help_text=_("The groups this user belongs to."),
|
|
29
|
+
related_name="fn_user_groups",
|
|
30
|
+
related_query_name="fn_user",
|
|
31
|
+
)
|
|
32
|
+
user_permissions = models.ManyToManyField(
|
|
33
|
+
"auth.Permission",
|
|
34
|
+
verbose_name="user permissions",
|
|
35
|
+
blank=True,
|
|
36
|
+
help_text=_("Specific permissions for this user."),
|
|
37
|
+
related_name="fn_user_permissions",
|
|
38
|
+
related_query_name="fn_user",
|
|
39
|
+
)
|
|
40
|
+
phone = models.CharField(
|
|
41
|
+
max_length=15, blank=True, null=True, verbose_name=_("Phone Number")
|
|
42
|
+
)
|
|
43
|
+
affiliation = models.CharField(
|
|
44
|
+
max_length=255, blank=True, null=True, verbose_name=_("Affiliation")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
superior_department = models.CharField(
|
|
48
|
+
max_length=255,
|
|
49
|
+
blank=True,
|
|
50
|
+
null=True,
|
|
51
|
+
verbose_name=_("Superior department"),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
date_of_birth = models.DateField(
|
|
55
|
+
blank=True, null=True, verbose_name=_("Date of Birth")
|
|
56
|
+
)
|
|
57
|
+
gender = models.CharField(
|
|
58
|
+
max_length=1,
|
|
59
|
+
choices=Gender.choices,
|
|
60
|
+
default=Gender.UNKNOWN,
|
|
61
|
+
verbose_name=_("Gender"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
address = models.CharField(
|
|
65
|
+
max_length=255, blank=True, null=True, verbose_name=_("Address")
|
|
66
|
+
)
|
|
67
|
+
avatar = models.ImageField(
|
|
68
|
+
upload_to="avatars/", blank=True, null=True, verbose_name=_("Avatar")
|
|
69
|
+
)
|
|
70
|
+
bio = models.TextField(
|
|
71
|
+
max_length=512, blank=True, verbose_name=_("Biography")
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
created_at = models.DateTimeField(
|
|
75
|
+
null=True, auto_now_add=True, verbose_name=_("Time of creating")
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
updated_at = models.DateTimeField(
|
|
79
|
+
null=True, auto_now=True, verbose_name=_("Time of updating")
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
class Meta:
|
|
83
|
+
verbose_name = _("User Information")
|
|
84
|
+
verbose_name_plural = _("User Information")
|
|
85
|
+
|
|
86
|
+
def __str__(self):
|
|
87
|
+
return _("{0}'s Information").format(self.username)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# The end.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from django.contrib.auth.models import User
|
|
2
|
+
from django.db.models.signals import post_save
|
|
3
|
+
from django.dispatch import receiver
|
|
4
|
+
|
|
5
|
+
from .models import Fnuser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@receiver(post_save, sender=User)
|
|
9
|
+
def create_user_fnprofile(sender, instance, created, **kwargs):
|
|
10
|
+
if created:
|
|
11
|
+
Fnuser.objects.create(user=instance)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@receiver(post_save, sender=User)
|
|
15
|
+
def save_user_fnprofile(sender, instance, **kwargs):
|
|
16
|
+
if hasattr(instance, "fnprofile"):
|
|
17
|
+
instance.fnprofile.save()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# The end.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{% extends "base/document.html" %}
|
|
2
|
+
{% load crispy_forms_tags %}
|
|
3
|
+
{% block title %}
|
|
4
|
+
{% trans "Create" %}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
{% block content %}
|
|
7
|
+
<h2>{% trans "Create new account." %}</h2>
|
|
8
|
+
<form method="post" action="{% url 'fnprofile:create' %}">
|
|
9
|
+
{% csrf_token %} {{ form|crispy }}
|
|
10
|
+
<button type="submit button" class="btn btn-outline-primary">
|
|
11
|
+
{% trans "Confirm" %}
|
|
12
|
+
</button>
|
|
13
|
+
</form>
|
|
14
|
+
<p>
|
|
15
|
+
{% trans "Already have an account?" %}
|
|
16
|
+
<a href="{% url 'fnprofile:log_in' %}">{% trans "Login here" %}</a>
|
|
17
|
+
</p>
|
|
18
|
+
{% endblock %}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% extends 'base/document.html' %}
|
|
2
|
+
{% block content %}
|
|
3
|
+
<h1>
|
|
4
|
+
{% blocktrans with user_name=fnprofile.user.username %} Hello, {{ user_name }}!
|
|
5
|
+
{% endblocktrans %}
|
|
6
|
+
</h1>
|
|
7
|
+
<p>Phone: {{ fnprofile.phone }}</p>
|
|
8
|
+
<p>Date of Birth: {{ fnprofile.date_of_birth }}</p>
|
|
9
|
+
<p>Affiliation: {{ fnprofile.affiliation }}</p>
|
|
10
|
+
<p>Superior Department: {{ fnprofile.superior_department }}</p>
|
|
11
|
+
<p>Date of Birth: {{ fnprofile.date_of_birth }}</p>
|
|
12
|
+
<p>Address: {{ fnprofile.address }}</p>
|
|
13
|
+
{% if fnprofile.avatar %}
|
|
14
|
+
<img src="{{ fnprofile.avatar.url }}" alt="Avatar" />
|
|
15
|
+
{% endif %}
|
|
16
|
+
{% endblock %}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{% extends "base/document.html" %}
|
|
2
|
+
{% load crispy_forms_tags %}
|
|
3
|
+
{% block title %}
|
|
4
|
+
{% trans "Update Profile" %}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
{% block content %}
|
|
7
|
+
<h2>{% trans "Update Profile." %}</h2>
|
|
8
|
+
<form method="post"
|
|
9
|
+
action="{% url 'fnprofile:update' %}"
|
|
10
|
+
enctype="multipart/form-data">
|
|
11
|
+
{% csrf_token %} {{ form|crispy }}
|
|
12
|
+
<button type="submit button" class="btn btn-outline-primary">
|
|
13
|
+
{% trans "Update" %}
|
|
14
|
+
</button>
|
|
15
|
+
</form>
|
|
16
|
+
{% endblock %}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{% extends "base/document.html" %}
|
|
2
|
+
{% load crispy_forms_tags %}
|
|
3
|
+
{% block title %}
|
|
4
|
+
{% trans "Log in" %}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
{% block content %}
|
|
7
|
+
<h2>{% trans "Log in" %}</h2>
|
|
8
|
+
{% if form.errors %}
|
|
9
|
+
<p>{% trans "Your username and password didn't match. Please try again." %}</p>
|
|
10
|
+
{% endif %}
|
|
11
|
+
<form method="post" action="{% url 'fnprofile:log_in' %}">
|
|
12
|
+
{% csrf_token %} {{ form|crispy }}
|
|
13
|
+
<input type="hidden" name="next" value="{{ request.GET.next }}" />
|
|
14
|
+
<button type="submit button" class="btn btn-outline-primary">
|
|
15
|
+
{% trans "Log in" %}
|
|
16
|
+
</button>
|
|
17
|
+
</form>
|
|
18
|
+
<p>
|
|
19
|
+
{% trans "Don't have an account?" %}
|
|
20
|
+
<a href="{% url 'fnprofile:create' %}">{% trans "Register here" %}</a>
|
|
21
|
+
</p>
|
|
22
|
+
{% endblock %}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{% extends "base/document.html" %}
|
|
2
|
+
{% block title %}
|
|
3
|
+
{% trans
|
|
4
|
+
"Logged Out" %}
|
|
5
|
+
{% endblock %}
|
|
6
|
+
{% block content %}
|
|
7
|
+
<h2>{% trans "Logged Out" %}</h2>
|
|
8
|
+
<p>{% trans "You have been successfully logged out." %}</p>
|
|
9
|
+
<p>
|
|
10
|
+
<a href="{% url 'fnprofile:log_in' %}">{% trans "Log in again" %}</a>
|
|
11
|
+
</p>
|
|
12
|
+
{% endblock %}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from django.urls import include, path
|
|
3
|
+
|
|
4
|
+
from . import views
|
|
5
|
+
|
|
6
|
+
app_name = "fnprofile"
|
|
7
|
+
|
|
8
|
+
urlpatterns = [
|
|
9
|
+
path("detail", views.fnprofile_edit, name="detail"),
|
|
10
|
+
path("log_out", views.fnprofile_log_out, name="log_out"),
|
|
11
|
+
path("create", views.fnprofile_new, name="create"),
|
|
12
|
+
path("log_in", views.fnprofile_log_in, name="log_in"),
|
|
13
|
+
path("update", views.fnprofile_edit, name="update"),
|
|
14
|
+
]
|
|
15
|
+
# The end.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from django.contrib import messages
|
|
2
|
+
from django.contrib.auth import authenticate, login, logout
|
|
3
|
+
from django.contrib.auth.decorators import login_required
|
|
4
|
+
from django.shortcuts import redirect, render
|
|
5
|
+
from django.urls import reverse_lazy
|
|
6
|
+
from django.views.generic import CreateView
|
|
7
|
+
from fnschool import _, count_chinese_characters
|
|
8
|
+
from fnschool.settings import LOGIN_URL
|
|
9
|
+
|
|
10
|
+
from .forms import FnuserForm, FnuserLoginForm
|
|
11
|
+
|
|
12
|
+
# Create your views here.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def fnprofile_new(request):
|
|
16
|
+
form = None
|
|
17
|
+
if request.method == "POST":
|
|
18
|
+
form = FnuserForm(request.POST)
|
|
19
|
+
if form.is_valid():
|
|
20
|
+
user = form.save(commit=False)
|
|
21
|
+
user.set_password(form.cleaned_data["password"])
|
|
22
|
+
user.username = form.cleaned_data["username"]
|
|
23
|
+
user.save()
|
|
24
|
+
login(request, user)
|
|
25
|
+
return redirect("home")
|
|
26
|
+
else:
|
|
27
|
+
form = FnuserForm()
|
|
28
|
+
|
|
29
|
+
return render(request, "fnprofile/create.html", {"form": form})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def fnprofile_log_in(request):
|
|
33
|
+
if request.method == "POST":
|
|
34
|
+
form = FnuserLoginForm(request, data=request.POST)
|
|
35
|
+
if form.is_valid():
|
|
36
|
+
username = form.cleaned_data.get("username")
|
|
37
|
+
password = form.cleaned_data.get("password")
|
|
38
|
+
user = authenticate(request, username=username, password=password)
|
|
39
|
+
if user is not None:
|
|
40
|
+
login(request, user)
|
|
41
|
+
next_url = request.POST.get("next") or reverse_lazy("home")
|
|
42
|
+
return redirect(next_url)
|
|
43
|
+
else:
|
|
44
|
+
form = FnuserLoginForm()
|
|
45
|
+
return render(request, "fnprofile/log_in.html", {"form": form})
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def fnprofile_log_out(request):
|
|
49
|
+
logout(request)
|
|
50
|
+
return redirect("home")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@login_required
|
|
54
|
+
def fnprofile_edit(request):
|
|
55
|
+
if request.method == "POST":
|
|
56
|
+
form = FnuserForm(request.POST, request.FILES, instance=request.user)
|
|
57
|
+
print(request.FILES)
|
|
58
|
+
if form.is_valid():
|
|
59
|
+
form.save()
|
|
60
|
+
messages.success(
|
|
61
|
+
request, _("Fnuser has been updated successfully!")
|
|
62
|
+
)
|
|
63
|
+
return redirect("home")
|
|
64
|
+
else:
|
|
65
|
+
form = FnuserForm(instance=request.user)
|
|
66
|
+
return render(request, "fnprofile/edit.html", {"form": form})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# The end.
|
fnschoo1/fnschool/settings.py
CHANGED
|
@@ -48,6 +48,7 @@ INSTALLED_APPS = [
|
|
|
48
48
|
"crispy_bootstrap5", # For Bootstrap 5
|
|
49
49
|
# fnschool apps.
|
|
50
50
|
"fnschool",
|
|
51
|
+
"fnprofile",
|
|
51
52
|
"profiles",
|
|
52
53
|
"canteen",
|
|
53
54
|
]
|
|
@@ -106,7 +107,8 @@ DATABASES = {
|
|
|
106
107
|
|
|
107
108
|
# Password validation
|
|
108
109
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
|
109
|
-
AUTH_USER_MODEL = "
|
|
110
|
+
AUTH_USER_MODEL = "fnprofile.Fnuser"
|
|
111
|
+
## AUTH_USER_MODEL = "profiles.Profile"
|
|
110
112
|
AUTH_PASSWORD_VALIDATORS = [
|
|
111
113
|
{
|
|
112
114
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
|
@@ -164,7 +166,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
|
164
166
|
MEDIA_URL = "/media/"
|
|
165
167
|
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
|
166
168
|
|
|
167
|
-
LOGIN_URL = reverse_lazy("
|
|
169
|
+
LOGIN_URL = reverse_lazy("fnprofile:log_in")
|
|
168
170
|
|
|
169
171
|
_settings_path = Path(__file__).parent / "_settings.py"
|
|
170
172
|
if _settings_path.exists():
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django settings for fnschool project.
|
|
3
|
+
|
|
4
|
+
Generated by 'django-admin startproject' using Django 4.2.24.
|
|
5
|
+
|
|
6
|
+
For more information on this file, see
|
|
7
|
+
https://docs.djangoproject.com/en/4.2/topics/settings/
|
|
8
|
+
|
|
9
|
+
For the full list of settings and their values, see
|
|
10
|
+
https://docs.djangoproject.com/en/4.2/ref/settings/
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from django.urls import reverse_lazy
|
|
17
|
+
from django.utils.translation import gettext_lazy as _
|
|
18
|
+
|
|
19
|
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
20
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Quick-start development settings - unsuitable for production
|
|
24
|
+
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
|
25
|
+
|
|
26
|
+
# SECURITY WARNING: keep the secret key used in production secret!
|
|
27
|
+
SECRET_KEY = (
|
|
28
|
+
"django-insecure-vt(#owf#cbx)yo$9m^=%&-heu&txuc23&a5b=a=u@=274)2!9w"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# SECURITY WARNING: don't run with debug turned on in production!
|
|
32
|
+
DEBUG = True
|
|
33
|
+
|
|
34
|
+
ALLOWED_HOSTS = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Application definition
|
|
38
|
+
|
|
39
|
+
INSTALLED_APPS = [
|
|
40
|
+
"django.contrib.admin",
|
|
41
|
+
"django.contrib.auth",
|
|
42
|
+
"django.contrib.contenttypes",
|
|
43
|
+
"django.contrib.sessions",
|
|
44
|
+
"django.contrib.messages",
|
|
45
|
+
"django.contrib.staticfiles",
|
|
46
|
+
# site apps.
|
|
47
|
+
"crispy_forms",
|
|
48
|
+
"crispy_bootstrap5", # For Bootstrap 5
|
|
49
|
+
# fnschool apps.
|
|
50
|
+
"fnschool",
|
|
51
|
+
"fnprofile",
|
|
52
|
+
"profiles",
|
|
53
|
+
"canteen",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
|
57
|
+
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
|
58
|
+
|
|
59
|
+
MIDDLEWARE = [
|
|
60
|
+
"django.middleware.security.SecurityMiddleware",
|
|
61
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
62
|
+
"django.middleware.locale.LocaleMiddleware",
|
|
63
|
+
"django.middleware.common.CommonMiddleware",
|
|
64
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
65
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
66
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
|
67
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
ROOT_URLCONF = "fnschool.urls"
|
|
71
|
+
|
|
72
|
+
TEMPLATES = [
|
|
73
|
+
{
|
|
74
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
75
|
+
"DIRS": [
|
|
76
|
+
os.path.join(BASE_DIR, "templates"),
|
|
77
|
+
],
|
|
78
|
+
"APP_DIRS": True,
|
|
79
|
+
"OPTIONS": {
|
|
80
|
+
"context_processors": [
|
|
81
|
+
"django.template.context_processors.debug",
|
|
82
|
+
"django.template.context_processors.request",
|
|
83
|
+
"django.contrib.auth.context_processors.auth",
|
|
84
|
+
"django.contrib.messages.context_processors.messages",
|
|
85
|
+
"django.template.context_processors.i18n",
|
|
86
|
+
],
|
|
87
|
+
"builtins": [
|
|
88
|
+
"django.templatetags.i18n",
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
WSGI_APPLICATION = "fnschool.wsgi.application"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Database
|
|
98
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
|
99
|
+
|
|
100
|
+
DATABASES = {
|
|
101
|
+
"default": {
|
|
102
|
+
"ENGINE": "django.db.backends.sqlite3",
|
|
103
|
+
"NAME": BASE_DIR / "db.sqlite3",
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Password validation
|
|
109
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
|
110
|
+
## AUTH_USER_MODEL = "fn_profile.FnUser"
|
|
111
|
+
AUTH_USER_MODEL = "profiles.Profile"
|
|
112
|
+
AUTH_PASSWORD_VALIDATORS = [
|
|
113
|
+
{
|
|
114
|
+
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
|
124
|
+
},
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Internationalization
|
|
129
|
+
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
|
130
|
+
|
|
131
|
+
USE_TZ = True
|
|
132
|
+
TIME_ZONE = "Asia/Shanghai"
|
|
133
|
+
|
|
134
|
+
USE_I18N = True
|
|
135
|
+
USE_L10N = True
|
|
136
|
+
|
|
137
|
+
LANGUAGE_CODE = "zh-hans"
|
|
138
|
+
|
|
139
|
+
LANGUAGES = [
|
|
140
|
+
("en", _("English")),
|
|
141
|
+
("es", _("Spanish")),
|
|
142
|
+
("fr", _("French")),
|
|
143
|
+
("zh-hans", _("Simplified Chinese")),
|
|
144
|
+
]
|
|
145
|
+
LOCALE_PATHS = [
|
|
146
|
+
os.path.join(BASE_DIR, "locale"),
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
USE_TZ = True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Static files (CSS, JavaScript, Images)
|
|
153
|
+
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
|
154
|
+
|
|
155
|
+
STATIC_URL = "/static/"
|
|
156
|
+
STATICFILES_DIRS = [
|
|
157
|
+
os.path.join(BASE_DIR, "static")
|
|
158
|
+
] # Optional: for additional static directories
|
|
159
|
+
STATIC_ROOT = os.path.join(BASE_DIR, "static_collected")
|
|
160
|
+
|
|
161
|
+
# Default primary key field type
|
|
162
|
+
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
|
163
|
+
|
|
164
|
+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
165
|
+
|
|
166
|
+
MEDIA_URL = "/media/"
|
|
167
|
+
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
|
168
|
+
|
|
169
|
+
LOGIN_URL = reverse_lazy("profile:log_in")
|
|
170
|
+
|
|
171
|
+
_settings_path = Path(__file__).parent / "_settings.py"
|
|
172
|
+
if _settings_path.exists():
|
|
173
|
+
print(
|
|
174
|
+
('Custom configuration "{_settings_path}" has been used.').format(
|
|
175
|
+
_settings_path=_settings_path.as_posix()
|
|
176
|
+
)
|
|
177
|
+
)
|
|
178
|
+
from ._settings import *
|
|
179
|
+
|
|
180
|
+
# The end.
|
fnschoo1/fnschool/urls.py
CHANGED
|
@@ -15,9 +15,12 @@ Including another URLconf
|
|
|
15
15
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
from django.conf import settings
|
|
18
19
|
from django.conf.urls.i18n import i18n_patterns
|
|
20
|
+
from django.conf.urls.static import static
|
|
19
21
|
from django.contrib import admin
|
|
20
|
-
from django.urls import include, path
|
|
22
|
+
from django.urls import include, path, re_path
|
|
23
|
+
from django.views.static import serve
|
|
21
24
|
|
|
22
25
|
from . import views
|
|
23
26
|
|
|
@@ -25,6 +28,8 @@ urlpatterns = [
|
|
|
25
28
|
path("", views.home_view, name="home"),
|
|
26
29
|
path("admin/", admin.site.urls),
|
|
27
30
|
path("i18n/", include("django.conf.urls.i18n")),
|
|
28
|
-
path("
|
|
31
|
+
path("fnprofile/", include("fnprofile.urls")),
|
|
29
32
|
path("canteen/", include("canteen.urls")),
|
|
30
33
|
]
|
|
34
|
+
if settings.DEBUG:
|
|
35
|
+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
Binary file
|
fnschoo1/profiles/__init__.py
CHANGED
fnschoo1/profiles/forms.py
CHANGED
|
@@ -36,6 +36,12 @@ class ProfileForm(ModelForm):
|
|
|
36
36
|
),
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
def __init__(self, *args, **kwargs):
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
self.fields["avatar"].widget.attrs.update(
|
|
42
|
+
{"class": "form-control-file"}
|
|
43
|
+
)
|
|
44
|
+
|
|
39
45
|
class Meta:
|
|
40
46
|
current_year = date.today().year
|
|
41
47
|
year_range = list(range(current_year - 100, current_year + 1))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Generated by Django 4.2.25 on 2025-10-24 01:44
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("profiles", "0005_alter_profile_gender"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="profile",
|
|
15
|
+
name="created_at",
|
|
16
|
+
field=models.DateTimeField(
|
|
17
|
+
auto_now_add=True, null=True, verbose_name="Time of creating"
|
|
18
|
+
),
|
|
19
|
+
),
|
|
20
|
+
migrations.AddField(
|
|
21
|
+
model_name="profile",
|
|
22
|
+
name="updated_at",
|
|
23
|
+
field=models.DateTimeField(
|
|
24
|
+
auto_now=True, null=True, verbose_name="更新时间"
|
|
25
|
+
),
|
|
26
|
+
),
|
|
27
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated by Django 4.2.25 on 2025-10-26 14:16
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("profiles", "0006_profile_created_at_profile_updated_at"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="profile",
|
|
15
|
+
name="created_at",
|
|
16
|
+
field=models.DateTimeField(
|
|
17
|
+
auto_now_add=True, null=True, verbose_name="创建时间"
|
|
18
|
+
),
|
|
19
|
+
),
|
|
20
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Generated by Django 4.2.25 on 2025-10-26 14:35
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
("auth", "0012_alter_user_first_name_max_length"),
|
|
10
|
+
("profiles", "0007_alter_profile_created_at"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AlterField(
|
|
15
|
+
model_name="profile",
|
|
16
|
+
name="groups",
|
|
17
|
+
field=models.ManyToManyField(
|
|
18
|
+
blank=True,
|
|
19
|
+
help_text="The groups this user belongs to.",
|
|
20
|
+
related_name="profile_groups",
|
|
21
|
+
related_query_name="profile",
|
|
22
|
+
to="auth.group",
|
|
23
|
+
verbose_name="groups",
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
migrations.AlterField(
|
|
27
|
+
model_name="profile",
|
|
28
|
+
name="user_permissions",
|
|
29
|
+
field=models.ManyToManyField(
|
|
30
|
+
blank=True,
|
|
31
|
+
help_text="这个用户的特定权限。",
|
|
32
|
+
related_name="profile_permissions",
|
|
33
|
+
related_query_name="profile",
|
|
34
|
+
to="auth.permission",
|
|
35
|
+
verbose_name="user permissions",
|
|
36
|
+
),
|
|
37
|
+
),
|
|
38
|
+
]
|