init
This commit is contained in:
30
.env.sample
Normal file
30
.env.sample
Normal file
@@ -0,0 +1,30 @@
|
||||
# should be prod or dev
|
||||
ENV='dev'
|
||||
|
||||
# secret
|
||||
SECRET='CHANGE_ME'
|
||||
|
||||
# Allowed hosts, domain's allowed, comma separated
|
||||
ALLOWED_HOSTS='127.0.0.1, localhost'
|
||||
|
||||
# CSRF Trusted origins
|
||||
CSRF_TRUSTED_ORIGINS='http://127.0.0.1, http://localhost'
|
||||
|
||||
### DEV RELATED
|
||||
# VITE
|
||||
DEV_SERVER_HOST='127.0.0.1'
|
||||
DEV_SERVER_PORT='8080'
|
||||
|
||||
### DB related
|
||||
# db engine, should be 'sqlite', 'pgsql' (psycopg[binary] required)
|
||||
DB_ENGINE='sqlite3'
|
||||
|
||||
# SQLITE
|
||||
SQLITE_REL_PATH='db.sqlite3'
|
||||
|
||||
# PostgreSQL, docker compose .env file convention
|
||||
#POSTGRES_HOST=localhost
|
||||
#POSTGRES_PORT=5432
|
||||
#POSTGRES_DB=changeme
|
||||
#POSTGRES_USER=changeme
|
||||
#POSTGRES_PASSWORD=changeme
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.idea/
|
||||
|
||||
db.sqlite3
|
||||
.env
|
||||
21
README.md
Normal file
21
README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## First run
|
||||
* install https://nodejs.org/en (tested with version 20)
|
||||
* install https://www.python.org/ (tested with version 3.12)
|
||||
* `cp .env.sample .env`
|
||||
* modify .env
|
||||
* follow requirements sections
|
||||
|
||||
## Requirements python
|
||||
* ### for dev : `pip install -r requirements/dev.txt`
|
||||
* ### for prod : `pip install -r requirements/prod.txt`
|
||||
|
||||
## Requirements nodejs
|
||||
* ### Linux `cd frontend && npm install && cd -`
|
||||
* ### Windows `pushd .; cd frontend; npm install; popd`
|
||||
|
||||
|
||||
## Run project
|
||||
* ### for dev :
|
||||
* in base dir `python manage.py runserver`
|
||||
* in frontend dir `npm run dev`
|
||||
* ### for prod : in frontend dir `npm run build`
|
||||
BIN
_static/images/no_image.png
Normal file
BIN
_static/images/no_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 751 KiB |
23
_static/images/no_mage_600_x_400.svg
Normal file
23
_static/images/no_mage_600_x_400.svg
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="600px" height="400px" viewBox="0.5 0.5 600 400" enable-background="new 0.5 0.5 600 400" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#454545" d="M43.261,165.3V76.771h12.478l28.372,44.79c6.567,10.376,11.69,19.702,15.893,28.765l0.263-0.132 c-1.051-11.821-1.314-22.592-1.314-36.384V76.77h10.77v88.53H98.165l-28.108-44.921c-6.173-9.851-12.084-19.965-16.55-29.553 l-0.394,0.131c0.657,11.165,0.92,21.804,0.92,36.515V165.3H43.261z"/>
|
||||
<path fill="#454545" d="M186.822,132.988c0,23.511-16.287,33.757-31.655,33.757c-17.207,0-30.473-12.61-30.473-32.707 c0-21.278,13.923-33.756,31.524-33.756C174.475,100.282,186.822,113.549,186.822,132.988z M136.384,133.645 c0,13.923,8.012,24.431,19.308,24.431c11.033,0,19.308-10.377,19.308-24.694c0-10.77-5.385-24.43-19.045-24.43 S136.384,121.561,136.384,133.645z"/>
|
||||
<path fill="#454545" d="M242.116,83.864c0.131,3.94-2.758,7.093-7.355,7.093c-4.072,0-6.962-3.153-6.962-7.093 c0-4.072,3.021-7.225,7.224-7.225C239.358,76.64,242.116,79.792,242.116,83.864z M229.244,165.3v-63.573h11.558V165.3H229.244z"/>
|
||||
<path fill="#454545" d="M259.978,118.934c0-6.568-0.132-11.953-0.526-17.207h10.114l0.526,10.245h0.394 c3.546-6.042,9.457-11.69,19.965-11.69c8.67,0,15.237,5.254,17.996,12.741h0.262c1.97-3.546,4.466-6.305,7.093-8.274 c3.809-2.89,8.012-4.466,14.055-4.466c8.406,0,20.884,5.517,20.884,27.584V165.3h-11.296v-35.989 c0-12.216-4.466-19.571-13.791-19.571c-6.568,0-11.69,4.86-13.661,10.508c-0.525,1.576-0.919,3.678-0.919,5.779V165.3h-11.296 v-38.091c0-10.114-4.467-17.469-13.267-17.469c-7.225,0-12.478,5.779-14.317,11.558c-0.657,1.708-0.919,3.678-0.919,5.648V165.3 h-11.296v-46.366C259.979,118.934,259.978,118.934,259.978,118.934z"/>
|
||||
<path fill="#454545" d="M404.722,165.3l-0.919-8.012h-0.393c-3.548,4.991-10.377,9.457-19.44,9.457 c-12.873,0-19.44-9.064-19.44-18.258c0-15.368,13.66-23.774,38.223-23.643v-1.314c0-5.254-1.445-14.711-14.449-14.711 c-5.91,0-12.083,1.839-16.55,4.729l-2.626-7.618c5.253-3.415,12.872-5.648,20.884-5.648c19.44,0,24.168,13.266,24.168,26.007 v23.774c0,5.517,0.262,10.902,1.051,15.237L404.722,165.3L404.722,165.3z M403.015,132.857 c-12.609-0.263-26.927,1.97-26.927,14.317c0,7.487,4.992,11.033,10.902,11.033c8.276,0,13.529-5.254,15.368-10.639 c0.394-1.182,0.657-2.496,0.657-3.678L403.015,132.857L403.015,132.857z"/>
|
||||
<path fill="#454545" d="M487.602,101.727c-0.262,4.597-0.525,9.72-0.525,17.469v36.909c0,14.579-2.891,23.512-9.063,29.028 c-6.173,5.779-15.106,7.618-23.118,7.618c-7.618,0-16.024-1.839-21.148-5.254l2.891-8.799c4.203,2.626,10.77,4.991,18.651,4.991 c11.821,0,20.49-6.173,20.49-22.197v-7.093h-0.262c-3.546,5.911-10.377,10.64-20.227,10.64c-15.762,0-27.057-13.398-27.057-30.999 c0-21.541,14.054-33.756,28.633-33.756c11.033,0,17.075,5.779,19.834,11.033h0.262l0.525-9.589L487.602,101.727L487.602,101.727z M475.649,126.815c0-1.97-0.132-3.678-0.657-5.254c-2.102-6.699-7.75-12.216-16.156-12.216c-11.033,0-18.915,9.326-18.915,24.037 c0,12.478,6.305,22.855,18.783,22.855c7.093,0,13.529-4.466,16.025-11.821c0.657-1.97,0.919-4.203,0.919-6.173v-11.428 C475.648,126.815,475.649,126.815,475.649,126.815z"/>
|
||||
<path fill="#454545" d="M512.686,135.615c0.264,15.63,10.246,22.066,21.804,22.066c8.274,0,13.266-1.445,17.601-3.284l1.969,8.275 c-4.071,1.839-11.032,3.941-21.146,3.941c-19.572,0-31.261-12.872-31.261-32.049s11.296-34.282,29.815-34.282 c20.753,0,26.27,18.257,26.27,29.948c0,2.364-0.263,4.203-0.394,5.385L512.686,135.615L512.686,135.615z M546.575,127.34 c0.131-7.356-3.021-18.783-16.025-18.783c-11.69,0-16.813,10.77-17.732,18.783H546.575z"/>
|
||||
<path fill="#454545" d="M100.266,322.915l-0.919-8.012h-0.394c-3.546,4.991-10.377,9.457-19.44,9.457 c-12.872,0-19.439-9.063-19.439-18.258c0-15.367,13.66-23.773,38.222-23.643v-1.314c0-5.254-1.445-14.71-14.449-14.71 c-5.91,0-12.084,1.839-16.55,4.728l-2.627-7.618c5.254-3.416,12.873-5.649,20.884-5.649c19.44,0,24.168,13.266,24.168,26.008 v23.773c0,5.517,0.263,10.902,1.051,15.237L100.266,322.915L100.266,322.915z M98.559,290.472 c-12.609-0.262-26.927,1.97-26.927,14.317c0,7.487,4.991,11.033,10.902,11.033c8.274,0,13.529-5.254,15.368-10.639 c0.394-1.183,0.657-2.495,0.657-3.678C98.559,301.505,98.559,290.472,98.559,290.472z"/>
|
||||
<path fill="#454545" d="M131.789,259.343l12.478,35.726c2.102,5.779,3.809,11.033,5.122,16.288h0.394 c1.445-5.254,3.283-10.508,5.385-16.288l12.347-35.726h12.084l-24.956,63.572H143.61l-24.168-63.572H131.789z"/>
|
||||
<path fill="#454545" d="M225.175,322.915l-0.919-8.012h-0.394c-3.546,4.991-10.377,9.457-19.44,9.457 c-12.872,0-19.439-9.063-19.439-18.258c0-15.367,13.66-23.773,38.222-23.643v-1.314c0-5.254-1.445-14.71-14.449-14.71 c-5.91,0-12.084,1.839-16.55,4.728l-2.627-7.618c5.254-3.416,12.873-5.649,20.884-5.649c19.44,0,24.168,13.266,24.168,26.008 v23.773c0,5.517,0.263,10.902,1.051,15.237L225.175,322.915L225.175,322.915z M223.468,290.472 c-12.609-0.262-26.927,1.97-26.927,14.317c0,7.487,4.991,11.033,10.902,11.033c8.274,0,13.529-5.254,15.368-10.639 c0.394-1.183,0.657-2.495,0.657-3.678L223.468,290.472L223.468,290.472z"/>
|
||||
<path fill="#454545" d="M266.154,241.479c0.131,3.941-2.758,7.093-7.355,7.093c-4.072,0-6.961-3.152-6.961-7.093 c0-4.072,3.021-7.225,7.224-7.225C263.396,234.255,266.154,237.407,266.154,241.479z M253.282,322.915v-63.572h11.558v63.572 H253.282z"/>
|
||||
<path fill="#454545" d="M284.016,229.657h11.558v93.258h-11.558V229.657z"/>
|
||||
<path fill="#454545" d="M350.214,322.915l-0.919-8.012h-0.393c-3.546,4.991-10.377,9.457-19.44,9.457 c-12.873,0-19.44-9.063-19.44-18.258c0-15.367,13.661-23.773,38.223-23.643v-1.314c0-5.254-1.445-14.71-14.449-14.71 c-5.91,0-12.083,1.839-16.55,4.728l-2.626-7.618c5.253-3.416,12.872-5.649,20.884-5.649c19.44,0,24.168,13.266,24.168,26.008 v23.773c0,5.517,0.262,10.902,1.051,15.237L350.214,322.915L350.214,322.915z M348.507,290.472 c-12.609-0.262-26.927,1.97-26.927,14.317c0,7.487,4.992,11.033,10.902,11.033c8.276,0,13.529-5.254,15.368-10.639 c0.394-1.183,0.657-2.495,0.657-3.678L348.507,290.472L348.507,290.472z"/>
|
||||
<path fill="#454545" d="M377.796,322.915c0.262-4.335,0.525-10.771,0.525-16.418v-76.84h11.428v39.93h0.262 c4.073-7.093,11.428-11.69,21.673-11.69c15.762,0,26.927,13.134,26.795,32.443c0,22.722-14.316,34.019-28.503,34.019 c-9.194,0-16.549-3.548-21.278-11.954h-0.394l-0.525,10.508h-9.983V322.915z M389.749,297.434c0,1.444,0.262,2.89,0.525,4.203 c2.233,8.012,8.932,13.529,17.339,13.529c12.083,0,19.308-9.852,19.308-24.43c0-12.741-6.568-23.643-18.914-23.643 c-7.881,0-15.237,5.385-17.601,14.185c-0.262,1.314-0.657,2.89-0.657,4.728L389.749,297.434L389.749,297.434z"/>
|
||||
<path fill="#454545" d="M453.055,229.657h11.56v93.258h-11.56V229.657z"/>
|
||||
<path fill="#454545" d="M490.488,293.23c0.264,15.63,10.246,22.067,21.804,22.067c8.274,0,13.266-1.445,17.601-3.284l1.969,8.274 c-4.071,1.839-11.032,3.941-21.146,3.941c-19.572,0-31.261-12.872-31.261-32.049s11.296-34.282,29.815-34.282 c20.753,0,26.27,18.258,26.27,29.947c0,2.365-0.262,4.203-0.394,5.386L490.488,293.23L490.488,293.23z M524.377,284.955 c0.131-7.355-3.021-18.782-16.025-18.782c-11.69,0-16.813,10.77-17.732,18.782H524.377z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.2 KiB |
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
16
app/asgi.py
Normal file
16
app/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for app project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
166
app/settings.py
Normal file
166
app/settings.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Django settings for app project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.0.4.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from dotenv import load_dotenv
|
||||
from os import getenv
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
load_dotenv(BASE_DIR / ".env")
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = getenv("SECRET", "not_secure")
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = getenv("ENV", "prod") == "dev"
|
||||
|
||||
ALLOWED_HOSTS = [host.strip() for host in getenv("ALLOWED_HOSTS").split(",") if host]
|
||||
CSRF_TRUSTED_ORIGINS = [host.strip() for host in getenv("CSRF_TRUSTED_ORIGINS").split(",") if host]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
"corsheaders",
|
||||
"django_vite",
|
||||
|
||||
"user",
|
||||
"home",
|
||||
"blog"
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'app.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [BASE_DIR / 'templates'],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'app.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||
|
||||
match getenv("DB_ENGINE"):
|
||||
case "pgsql":
|
||||
DB = {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": getenv("DB_NAME"),
|
||||
"USER": getenv("DB_USER"),
|
||||
"PASSWORD": getenv("DB_PASSWORD"),
|
||||
"HOST": getenv("DB_HOST", "localhost"),
|
||||
"PORT": int(getenv("DB_PORT", 5432)),
|
||||
}
|
||||
case _:
|
||||
DB = {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / getenv("SQLITE_REL_PATH", "db.sqlite3"),
|
||||
}
|
||||
|
||||
DATABASES = {
|
||||
'default': DB
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
|
||||
TIME_ZONE = 'Europe/Paris'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATICFILES_DIRS = []
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
AUTH_USER_MODEL = "user.User"
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
LOGIN_URL = "/user/login/"
|
||||
LOGOUT_REDIRECT_URL = "/user/login/"
|
||||
SESSION_COOKIE_AGE = 60 * 60 * 24 * 30
|
||||
|
||||
DJANGO_VITE = {
|
||||
"default": {
|
||||
"dev_mode": DEBUG,
|
||||
"manifest_path": BASE_DIR / "frontend/dist/manifest.json",
|
||||
"dev_server_host": getenv("DEV_SERVER_HOST", "localhost"),
|
||||
"dev_server_port": int(getenv("DEV_SERVER_PORT", 8080)),
|
||||
}
|
||||
}
|
||||
STATICFILES_DIRS += BASE_DIR / "frontend/dist",
|
||||
|
||||
25
app/urls.py
Normal file
25
app/urls.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
URL configuration for app project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
|
||||
path("", include("home.urls", namespace="home")),
|
||||
path("blog/", include("blog.urls", namespace="blog")),
|
||||
]
|
||||
16
app/wsgi.py
Normal file
16
app/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for app project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
0
blog/__init__.py
Normal file
0
blog/__init__.py
Normal file
26
blog/admin.py
Normal file
26
blog/admin.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from ordered_model.admin import OrderedModelAdmin
|
||||
|
||||
from .models import Article, Tag, Category
|
||||
from .forms import ArticleForm
|
||||
|
||||
|
||||
@admin.register(Article)
|
||||
class ArticleAdmin(admin.ModelAdmin):
|
||||
form = ArticleForm
|
||||
list_display = ('title', 'description', 'author')
|
||||
exclude = ["author"]
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.author = request.user
|
||||
return super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(OrderedModelAdmin):
|
||||
list_display = ('name', 'move_up_down_links')
|
||||
|
||||
|
||||
admin.site.register(Tag)
|
||||
|
||||
6
blog/apps.py
Normal file
6
blog/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class BlogConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'blog'
|
||||
13
blog/forms.py
Normal file
13
blog/forms.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django import forms
|
||||
from tinymce.widgets import TinyMCE
|
||||
|
||||
from blog.models import Article
|
||||
|
||||
|
||||
class ArticleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Article
|
||||
exclude = "__all__"
|
||||
widgets = {
|
||||
"content": TinyMCE()
|
||||
}
|
||||
32
blog/jinja2/blog/article_detail.html
Normal file
32
blog/jinja2/blog/article_detail.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "blog/layout.html" %}
|
||||
|
||||
{% block css %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/toolbar/prism-toolbar.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ super() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/toolbar/prism-toolbar.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/show-language/prism-show-language.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h2>
|
||||
{{ article.title }}
|
||||
</h2>
|
||||
</header>
|
||||
<p>{{ article.description.replace("\n", "<br />")|safe }}</p>
|
||||
<div class="blog_content line-numbers">
|
||||
{{ article.content|safe }}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
34
blog/jinja2/blog/article_list.html
Normal file
34
blog/jinja2/blog/article_list.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends "blog/layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="article_list">
|
||||
{% for article in articles %}
|
||||
<a href="{{ url("blog:article_detail", args=[article.pk, slugify(article.title)]) }}">
|
||||
<article>
|
||||
{% set article_image %}
|
||||
<section class="article_image">
|
||||
{# <img src="{{ static("images/no_mage_600_x_400.svg") }}">#}
|
||||
<img src="{{ static("images/no_image.png") }}" alt="No image"/>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{% set article_text %}
|
||||
<section class="article_text">
|
||||
<header>
|
||||
<h2>{{ article.title }}</h2>
|
||||
</header>
|
||||
<p>{{ article.description.replace("\n", "<br/>")|safe }}</p>
|
||||
</section>
|
||||
{% endset %}
|
||||
|
||||
{{ article_image }}
|
||||
{{ article_text }}
|
||||
</article>
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<p>Aucun article</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
11
blog/jinja2/blog/layout.html
Normal file
11
blog/jinja2/blog/layout.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block css %}
|
||||
{{ super() }}
|
||||
{{ stylesheet_pack("blog") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ super() }}
|
||||
{{ javascript_pack("blog") }}
|
||||
{% endblock %}
|
||||
54
blog/migrations/0001_initial.py
Normal file
54
blog/migrations/0001_initial.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-19 08:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Article',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255)),
|
||||
('description', models.TextField()),
|
||||
('content', models.TextField()),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_modified', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Categories',
|
||||
'ordering': ('order',),
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Comment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('content', models.TextField()),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
('date_modified', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
],
|
||||
),
|
||||
]
|
||||
48
blog/migrations/0002_initial.py
Normal file
48
blog/migrations/0002_initial.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-19 08:31
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('blog', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='author',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='comment',
|
||||
name='article',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='comment',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='comment',
|
||||
name='response_to',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.comment'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='article',
|
||||
name='tags',
|
||||
field=models.ManyToManyField(blank=True, to='blog.tag'),
|
||||
),
|
||||
]
|
||||
0
blog/migrations/__init__.py
Normal file
0
blog/migrations/__init__.py
Normal file
44
blog/models.py
Normal file
44
blog/models.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from django.db import models
|
||||
|
||||
from ordered_model.models import OrderedModel
|
||||
|
||||
|
||||
class Article(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField()
|
||||
content = models.TextField()
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
category = models.ForeignKey("Category", on_delete=models.CASCADE, null=True, blank=True)
|
||||
tags = models.ManyToManyField("Tag", blank=True)
|
||||
author = models.ForeignKey("user.User", on_delete=models.CASCADE, null=True, blank=True)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class Category(OrderedModel):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
class Meta(OrderedModel.Meta):
|
||||
verbose_name_plural = "Categories"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Tag(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Comment(models.Model):
|
||||
author = models.ForeignKey("user.User", on_delete=models.CASCADE)
|
||||
article = models.ForeignKey("Article", on_delete=models.CASCADE)
|
||||
content = models.TextField()
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
response_to = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True)
|
||||
32
blog/templates/blog/article_detail.html
Normal file
32
blog/templates/blog/article_detail.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "blog/layout.html" %}
|
||||
|
||||
{% block css %}
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/toolbar/prism-toolbar.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ super() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/toolbar/prism-toolbar.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/show-language/prism-show-language.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
<h2>
|
||||
{{ article.title }}
|
||||
</h2>
|
||||
</header>
|
||||
<p>{{ article.description.replace("\n", "<br />")|safe }}</p>
|
||||
<div class="blog_content line-numbers">
|
||||
{{ article.content|safe }}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
34
blog/templates/blog/article_list.html
Normal file
34
blog/templates/blog/article_list.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends "blog/layout.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div id="article_list">
|
||||
{% for article in articles %}
|
||||
<a href="{% url "blog:article_detail" article.pk article.title|slugify %}">
|
||||
<article>
|
||||
{# {% set article_image %}#}
|
||||
{# <section class="article_image">#}
|
||||
{# <img src="{{ static("images/no_mage_600_x_400.svg") }}">#}
|
||||
{# <img src="{% static "images/no_image.png" %}" alt="No image"/>#}
|
||||
{# </section>#}
|
||||
{# {% endset %}#}
|
||||
{##}
|
||||
{# {% set article_text %}#}
|
||||
{# <section class="article_text">#}
|
||||
{# <header>#}
|
||||
{# <h2>{{ article.title }}</h2>#}
|
||||
{# </header>#}
|
||||
{# <p>{{ article.description|safe|linebreaks }}</p>#}
|
||||
{# </section>#}
|
||||
{# {% endset %}#}
|
||||
|
||||
{# {{ article_image }}#}
|
||||
{# {{ article_text }}#}
|
||||
</article>
|
||||
</a>
|
||||
{% empty %}
|
||||
<p>Aucun article</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
11
blog/templates/blog/layout.html
Normal file
11
blog/templates/blog/layout.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block css %}
|
||||
{{ super }}
|
||||
{# {{ stylesheet_pack("blog") }}#}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ super }}
|
||||
{# {{ javascript_pack("blog") }}#}
|
||||
{% endblock %}
|
||||
3
blog/tests.py
Normal file
3
blog/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
9
blog/urls.py
Normal file
9
blog/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'blog'
|
||||
urlpatterns = [
|
||||
path("", views.ArticleListView.as_view(), name="article_list"),
|
||||
path("<int:pk>-<str:slug>/", views.ArticleDetailView.as_view(), name="article_detail"),
|
||||
]
|
||||
15
blog/views.py
Normal file
15
blog/views.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.views.generic import ListView, DetailView
|
||||
|
||||
from blog.models import Article
|
||||
|
||||
|
||||
class ArticleListView(ListView):
|
||||
model = Article
|
||||
template_name = 'blog/article_list.html'
|
||||
context_object_name = 'articles'
|
||||
|
||||
|
||||
class ArticleDetailView(DetailView):
|
||||
model = Article
|
||||
template_name = 'blog/article_detail.html'
|
||||
context_object_name = 'article'
|
||||
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1098
frontend/package-lock.json
generated
Normal file
1098
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
frontend/package.json
Normal file
19
frontend/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.19",
|
||||
"normalize.css": "^8.0.1",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
}
|
||||
7
frontend/postcss.config.cjs
Normal file
7
frontend/postcss.config.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = (ctx) => ({
|
||||
plugins: [
|
||||
require('postcss-import')(),
|
||||
require('postcss-simple-vars')(),
|
||||
require("autoprefixer")(),
|
||||
]
|
||||
})
|
||||
35
frontend/src/application/app.js
Normal file
35
frontend/src/application/app.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'vite/modulepreload-polyfill'
|
||||
// import "../styles/app.css"
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
(function(){
|
||||
// DOM is ready.
|
||||
setTimeout(async () => {
|
||||
const response = await fetch("/hit/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
'X-CSRFToken': getCookie("csrftoken")
|
||||
},
|
||||
mode: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
|
||||
})
|
||||
});
|
||||
}, 1000)
|
||||
})();
|
||||
5
frontend/src/application/app2.js
Normal file
5
frontend/src/application/app2.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import "../components/sidebar";
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", function () {
|
||||
window.console.log("dom ready 2");
|
||||
});
|
||||
2
frontend/src/application/blog.js
Normal file
2
frontend/src/application/blog.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import "../styles/blog.css"
|
||||
|
||||
1
frontend/src/application/cv.js
Normal file
1
frontend/src/application/cv.js
Normal file
@@ -0,0 +1 @@
|
||||
// import "../styles/cv.css"
|
||||
1
frontend/src/components/sidebar.js
Normal file
1
frontend/src/components/sidebar.js
Normal file
@@ -0,0 +1 @@
|
||||
window.console.log("sidebar is loaded");
|
||||
48
frontend/src/styles/app.css
Normal file
48
frontend/src/styles/app.css
Normal file
@@ -0,0 +1,48 @@
|
||||
@import "normalize.css";
|
||||
|
||||
html::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html{
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
body{
|
||||
background-color: rgba(43, 43, 42, 0.8);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
ul{
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#main_header{
|
||||
background-color: white;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#main_header nav{
|
||||
max-width: 90%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#main_header ul{
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#main_header li {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#main_app{
|
||||
max-width: 90%;
|
||||
min-height: 50rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
background-color: white;
|
||||
}
|
||||
38
frontend/src/styles/blog.css
Normal file
38
frontend/src/styles/blog.css
Normal file
@@ -0,0 +1,38 @@
|
||||
/* hello */
|
||||
|
||||
#article_list{
|
||||
/*background-color: grey;*/
|
||||
}
|
||||
|
||||
#article_list article{
|
||||
/*background-color: green;*/
|
||||
display: flex;
|
||||
border: 0.1rem solid green;
|
||||
margin: 1rem;
|
||||
/*height: 10rem;*/
|
||||
}
|
||||
|
||||
#article_list a{
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.article_image{
|
||||
width: 30%;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.article_image img{
|
||||
/*image-;*/
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.article_text{
|
||||
width: 70%;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.article_text h2{
|
||||
text-align: center;
|
||||
}
|
||||
150
frontend/src/styles/cv.css
Normal file
150
frontend/src/styles/cv.css
Normal file
@@ -0,0 +1,150 @@
|
||||
#cv_main{
|
||||
display: flex;
|
||||
justify-content: normal;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#cv_main > #cv_left{
|
||||
background-color: beige;
|
||||
max-width: 30%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
#cv_main > #cv_right{
|
||||
background-color: cadetblue;
|
||||
min-width: 70%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
article header{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: #e0e0e0;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
article header>div:first-child{
|
||||
margin-left: 1rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cv_header_title{
|
||||
margin: auto;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cv_header_title>*{
|
||||
padding: 0;
|
||||
margin: 0.1rem;
|
||||
}
|
||||
|
||||
.cv_content_competence{
|
||||
max-width: 30%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 2fr);
|
||||
column-gap: 1rem;
|
||||
row-gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*article header{*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: row;*/
|
||||
/* align-items: center;*/
|
||||
/* background-color: #e0e0e0;*/
|
||||
/* justify-content: center;*/
|
||||
/* position: relative;*/
|
||||
/*}*/
|
||||
/*article header>div:first-child{*/
|
||||
/* margin-left: 1rem;*/
|
||||
/*}*/
|
||||
|
||||
/*.cv_header_title{*/
|
||||
/* margin: auto;*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/* align-items: center;*/
|
||||
|
||||
/*}*/
|
||||
|
||||
/*.cv_header_title>*{*/
|
||||
/* padding: 0;*/
|
||||
/* margin: 0.1rem;*/
|
||||
/*}*/
|
||||
/*.g_pas_idee {*/
|
||||
/* position: absolute;*/
|
||||
/* left: 0;*/
|
||||
/* background-color: red;*/
|
||||
/* text-align: center;*/
|
||||
/*}*/
|
||||
|
||||
/*@media screen and (max-width: 700px) {*/
|
||||
/* .g_pas_idee {*/
|
||||
/* position: inherit;*/
|
||||
/* width: 100%;*/
|
||||
/* }*/
|
||||
/* article header{*/
|
||||
/* flex-direction: column;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
|
||||
|
||||
/*article header{*/
|
||||
/* display: grid;*/
|
||||
/* grid-template-columns: 3fr 5fr;*/
|
||||
/* align-items: center;*/
|
||||
/* background-color: #e0e0e0;*/
|
||||
/*}*/
|
||||
|
||||
/*article header>div:first-child{*/
|
||||
/* margin-left: 1rem;*/
|
||||
/*}*/
|
||||
|
||||
/*.cv_header_title{*/
|
||||
/* text-align: center;*/
|
||||
/* margin-right: auto;*/
|
||||
/*}*/
|
||||
|
||||
/*.cv_header_title>h2,h3{*/
|
||||
/* margin: .5rem;*/
|
||||
/*}*/
|
||||
|
||||
/*article header>div{*/
|
||||
/* max-width: 60%;*/
|
||||
/* text-align: center;*/
|
||||
/*}*/
|
||||
|
||||
/*.cv_header_title{*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/*}*/
|
||||
|
||||
.cv_content h4 {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 800px) {
|
||||
#cv_main{
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#cv_main > #cv_left{
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
#cv_main > #cv_right{
|
||||
min-width: 90%;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
17
frontend/src/styles/index.scss
Normal file
17
frontend/src/styles/index.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
//.jumbotron {
|
||||
// // should be relative path of the entry scss file
|
||||
// background-image: url("../../vendors/images/sample.jpg");
|
||||
// background-size: cover;
|
||||
//}
|
||||
//
|
||||
//.btn-blue {
|
||||
// @apply inline-block px-4 py-2;
|
||||
// @apply font-semibold rounded-lg shadow-md;
|
||||
// @apply bg-blue-500 text-white;
|
||||
// @apply hover:bg-blue-700 focus:outline-none;
|
||||
// @apply focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
|
||||
//}
|
||||
49
frontend/vite.config.js
Normal file
49
frontend/vite.config.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import {defineConfig, loadEnv} from "vite";
|
||||
import {resolve, join} from "path";
|
||||
|
||||
// const postcssConfig = {
|
||||
// plugins: [
|
||||
// require('postcss-import')(),
|
||||
// require('postcss-simple-vars')(),
|
||||
// require('autoprefixer')(),
|
||||
// ],
|
||||
// };
|
||||
|
||||
export default defineConfig((mode) => {
|
||||
const env = loadEnv(mode, "..", ""),
|
||||
SRC_DIR = resolve("./src"),
|
||||
OUT_DIR = resolve("./dist")
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(SRC_DIR)
|
||||
}
|
||||
},
|
||||
root: SRC_DIR,
|
||||
base: "/static/",
|
||||
// css: {
|
||||
// postcss: postcssConfig
|
||||
// },
|
||||
server: {
|
||||
host: env.DEV_SERVER_HOST,
|
||||
port: env.DEV_SERVER_PORT
|
||||
},
|
||||
build: {
|
||||
manifest: "manifest.json",
|
||||
emptyOutDir: true,
|
||||
outDir: OUT_DIR,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
app: join(SRC_DIR, "application/app.js"),
|
||||
app2: join(SRC_DIR, "application/app2.js"),
|
||||
blog: join(SRC_DIR, "application/blog.js"),
|
||||
cv: join(SRC_DIR, "application/cv.js")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
241
frontend/yarn.lock
Normal file
241
frontend/yarn.lock
Normal file
@@ -0,0 +1,241 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@esbuild/win32-x64@0.20.2":
|
||||
version "0.20.2"
|
||||
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz"
|
||||
integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.17.2":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz"
|
||||
integrity sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==
|
||||
|
||||
"@types/estree@1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz"
|
||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||
|
||||
autoprefixer@^10.4.19:
|
||||
version "10.4.19"
|
||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz"
|
||||
integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==
|
||||
dependencies:
|
||||
browserslist "^4.23.0"
|
||||
caniuse-lite "^1.0.30001599"
|
||||
fraction.js "^4.3.7"
|
||||
normalize-range "^0.1.2"
|
||||
picocolors "^1.0.0"
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
browserslist@^4.23.0, "browserslist@>= 4.21.0":
|
||||
version "4.23.0"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz"
|
||||
integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30001587"
|
||||
electron-to-chromium "^1.4.668"
|
||||
node-releases "^2.0.14"
|
||||
update-browserslist-db "^1.0.13"
|
||||
|
||||
caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599:
|
||||
version "1.0.30001617"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz"
|
||||
integrity sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==
|
||||
|
||||
electron-to-chromium@^1.4.668:
|
||||
version "1.4.762"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz"
|
||||
integrity sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ==
|
||||
|
||||
esbuild@^0.20.1:
|
||||
version "0.20.2"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz"
|
||||
integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
|
||||
optionalDependencies:
|
||||
"@esbuild/aix-ppc64" "0.20.2"
|
||||
"@esbuild/android-arm" "0.20.2"
|
||||
"@esbuild/android-arm64" "0.20.2"
|
||||
"@esbuild/android-x64" "0.20.2"
|
||||
"@esbuild/darwin-arm64" "0.20.2"
|
||||
"@esbuild/darwin-x64" "0.20.2"
|
||||
"@esbuild/freebsd-arm64" "0.20.2"
|
||||
"@esbuild/freebsd-x64" "0.20.2"
|
||||
"@esbuild/linux-arm" "0.20.2"
|
||||
"@esbuild/linux-arm64" "0.20.2"
|
||||
"@esbuild/linux-ia32" "0.20.2"
|
||||
"@esbuild/linux-loong64" "0.20.2"
|
||||
"@esbuild/linux-mips64el" "0.20.2"
|
||||
"@esbuild/linux-ppc64" "0.20.2"
|
||||
"@esbuild/linux-riscv64" "0.20.2"
|
||||
"@esbuild/linux-s390x" "0.20.2"
|
||||
"@esbuild/linux-x64" "0.20.2"
|
||||
"@esbuild/netbsd-x64" "0.20.2"
|
||||
"@esbuild/openbsd-x64" "0.20.2"
|
||||
"@esbuild/sunos-x64" "0.20.2"
|
||||
"@esbuild/win32-arm64" "0.20.2"
|
||||
"@esbuild/win32-ia32" "0.20.2"
|
||||
"@esbuild/win32-x64" "0.20.2"
|
||||
|
||||
escalade@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz"
|
||||
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
|
||||
|
||||
fraction.js@^4.3.7:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
|
||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||
|
||||
function-bind@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
|
||||
|
||||
hasown@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
|
||||
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
is-core-module@^2.13.0:
|
||||
version "2.13.1"
|
||||
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz"
|
||||
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
|
||||
dependencies:
|
||||
hasown "^2.0.0"
|
||||
|
||||
nanoid@^3.3.7:
|
||||
version "3.3.7"
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
|
||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||
|
||||
node-releases@^2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz"
|
||||
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
|
||||
|
||||
normalize-range@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
|
||||
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
||||
|
||||
normalize.css@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz"
|
||||
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
|
||||
|
||||
path-parse@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
|
||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
|
||||
integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
|
||||
|
||||
postcss-import@^16.1.0:
|
||||
version "16.1.0"
|
||||
resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz"
|
||||
integrity sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==
|
||||
dependencies:
|
||||
postcss-value-parser "^4.0.0"
|
||||
read-cache "^1.0.0"
|
||||
resolve "^1.1.7"
|
||||
|
||||
postcss-simple-vars@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz"
|
||||
integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==
|
||||
|
||||
postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.1, postcss@^8.4.38:
|
||||
version "8.4.38"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz"
|
||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.2.0"
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz"
|
||||
integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
|
||||
dependencies:
|
||||
pify "^2.3.0"
|
||||
|
||||
resolve@^1.1.7:
|
||||
version "1.22.8"
|
||||
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz"
|
||||
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
|
||||
dependencies:
|
||||
is-core-module "^2.13.0"
|
||||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
rollup@^4.13.0:
|
||||
version "4.17.2"
|
||||
resolved "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz"
|
||||
integrity sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.5"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.17.2"
|
||||
"@rollup/rollup-android-arm64" "4.17.2"
|
||||
"@rollup/rollup-darwin-arm64" "4.17.2"
|
||||
"@rollup/rollup-darwin-x64" "4.17.2"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.17.2"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.17.2"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.17.2"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.17.2"
|
||||
"@rollup/rollup-linux-powerpc64le-gnu" "4.17.2"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.17.2"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.17.2"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.17.2"
|
||||
"@rollup/rollup-linux-x64-musl" "4.17.2"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.17.2"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.17.2"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.17.2"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
source-map-js@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz"
|
||||
integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
|
||||
|
||||
supports-preserve-symlinks-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
update-browserslist-db@^1.0.13:
|
||||
version "1.0.15"
|
||||
resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz"
|
||||
integrity sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==
|
||||
dependencies:
|
||||
escalade "^3.1.2"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
vite@^5.2.0:
|
||||
version "5.2.11"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz"
|
||||
integrity sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==
|
||||
dependencies:
|
||||
esbuild "^0.20.1"
|
||||
postcss "^8.4.38"
|
||||
rollup "^4.13.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
0
home/__init__.py
Normal file
0
home/__init__.py
Normal file
3
home/admin.py
Normal file
3
home/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
home/apps.py
Normal file
6
home/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'home'
|
||||
8
home/forms.py
Normal file
8
home/forms.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django import forms
|
||||
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
full_name = forms.CharField(max_length=255, required=True, label="Nom/Prénom ou entreprise")
|
||||
email = forms.EmailField(required=True)
|
||||
subject = forms.CharField(max_length=255, required=True, label="Sujet")
|
||||
message = forms.CharField(widget=forms.Textarea, required=True)
|
||||
24
home/middleware.py
Normal file
24
home/middleware.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from django.middleware.csrf import rotate_token
|
||||
|
||||
|
||||
class SessionOnHitMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
if not request.user.is_authenticated and not request.session.session_key:
|
||||
request.session.save()
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
|
||||
class CsrfOnHitMiddleWare:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
if 'CSRF_COOKIE' not in request.META:
|
||||
rotate_token(request)
|
||||
response = self.get_response(request)
|
||||
|
||||
return response
|
||||
27
home/migrations/0001_initial.py
Normal file
27
home/migrations/0001_initial.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-19 08:31
|
||||
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Hit',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('path', models.CharField(max_length=255)),
|
||||
('parameters', models.JSONField(default=dict)),
|
||||
('session_id', models.CharField(max_length=255)),
|
||||
('ip_address', models.GenericIPAddressField()),
|
||||
('user_agent', models.CharField(max_length=255)),
|
||||
('date_created', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
home/migrations/__init__.py
Normal file
0
home/migrations/__init__.py
Normal file
15
home/models.py
Normal file
15
home/models.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.db import models
|
||||
|
||||
import uuid
|
||||
|
||||
|
||||
class Hit(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
path = models.CharField(max_length=255)
|
||||
parameters = models.JSONField(default=dict)
|
||||
session_id = models.CharField(max_length=255)
|
||||
ip_address = models.GenericIPAddressField(max_length=255)
|
||||
user_agent = models.CharField(max_length=255)
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
28
home/templates/home/contact.html
Normal file
28
home/templates/home/contact.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% extends "home/layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" action="{% url "home:contact" %}">
|
||||
{{ csrf_input }}
|
||||
<div id="form_{{ form.full_name.id_for_label }}">
|
||||
{{ form.full_name.errors }}
|
||||
{{ form.full_name.label_tag }}
|
||||
{{ form.full_name }}
|
||||
</div>
|
||||
<div id="form_{{ form.email.id_for_label }}">
|
||||
{{ form.email.errors }}
|
||||
{{ form.email.label_tag }}
|
||||
{{ form.email }}
|
||||
</div>
|
||||
<div id="form_{{ form.subject.id_for_label }}">
|
||||
{{ form.subject.errors }}
|
||||
{{ form.subject.label_tag }}
|
||||
{{ form.subject }}
|
||||
</div>
|
||||
<div id="form_{{ form.message.id_for_label }}">
|
||||
{{ form.message.errors }}
|
||||
{{ form.message.label_tag }}
|
||||
{{ form.message }}
|
||||
</div>
|
||||
<input type="submit" value="Envoyer">
|
||||
</form>
|
||||
{% endblock %}
|
||||
5
home/templates/home/contact_success.html
Normal file
5
home/templates/home/contact_success.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends "home/layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% endblock %}
|
||||
204
home/templates/home/cv.html
Normal file
204
home/templates/home/cv.html
Normal file
@@ -0,0 +1,204 @@
|
||||
{% extends "home/layout.html" %}
|
||||
{% load django_vite %}
|
||||
|
||||
{% block css %}
|
||||
{{ super }}
|
||||
<link rel="stylesheet" href="{% vite_asset_url "styles/cv.css" %}">
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{% vite_asset "application/cv.js" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="cv_main">
|
||||
<div id="cv_left">
|
||||
<div id="cv_presentation">
|
||||
<h2>Fabien ROUSSEAU</h2>
|
||||
<h3>Développeur Python/Django</h3>
|
||||
<p>
|
||||
Passionné d'informatique. J'ai décidé d'en faire mon métier en apprenant par moi-même l'électronique, le réseau puis par la suite le développement
|
||||
Aujourd'hui je souhaiterais poursuivre ce parcours au sein d'une entreprise dont je partage les valeurs qui m'accompagnera dans un processus de validation et certification de mon expérience et de mes compétences
|
||||
</p>
|
||||
</div>
|
||||
<div id="cv_contact">
|
||||
<h3>Coodonnées</h3>
|
||||
<ul>
|
||||
<li><a href="https://www.linkedin.com/in/44-fabien-rousseau/">LinkedIN</a></li>
|
||||
<li><a href="{% url "home:contact" %}">Me contacter</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="cv_competence">
|
||||
<h3>Compétences</h3>
|
||||
<h4>Language et framework</h4>
|
||||
<ul>
|
||||
<li>Python</li>
|
||||
<li>Django</li>
|
||||
<li>Django Rest Framework</li>
|
||||
<li>Django Channels</li>
|
||||
<li>Jinja</li>
|
||||
<li>Celery</li>
|
||||
<li>VueJS</li>
|
||||
<li>NuxtJS</li>
|
||||
<li>Vuetify</li>
|
||||
<li>OCPP</li>
|
||||
</ul>
|
||||
<h4>Système d'exploitation</h4>
|
||||
<ul>
|
||||
<li>Linux</li>
|
||||
<li>Windows</li>
|
||||
</ul>
|
||||
<h4>Outils</h4>
|
||||
<ul>
|
||||
<li>Docker</li>
|
||||
<li>Git</li>
|
||||
<li>Kanban</li>
|
||||
</ul>
|
||||
<h4>Base de données</h4>
|
||||
<ul>
|
||||
<li>PostgreSQL</li>
|
||||
<li>MySQL</li>
|
||||
<li>Redis</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="cv_strong">
|
||||
<h3>Points forts</h3>
|
||||
<ul>
|
||||
<li>Passionné de développement</li>
|
||||
<li>Polyvalent et adaptabilité</li>
|
||||
<li>Travaille efficacement en équipe</li>
|
||||
<li>Consciencieux</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cv_right">
|
||||
<article>
|
||||
<header>
|
||||
<div>
|
||||
<p>Avril 2017 à Maintenant</p>
|
||||
</div>
|
||||
<div class="cv_header_title">
|
||||
<h2>Développeur</h2>
|
||||
<h3>Auto-entrepreneur - Nantes - Full remote</h3>
|
||||
</div>
|
||||
</header>
|
||||
<div class="cv_content">
|
||||
<h4>Services : </h4>
|
||||
<ul>
|
||||
<li>Développement applicatif</li>
|
||||
<li>Conseil en informatique</li>
|
||||
<li>Hébergement et maintenance web</li>
|
||||
</ul>
|
||||
<h4>Missions : </h4>
|
||||
<ul>
|
||||
<li>Site web de suivi d’actualité footballistique</li>
|
||||
<li>Site de suivi des résultats et statistique footballistique</li>
|
||||
<li>Site d’hébergement de CV pour sportifs amateurs</li>
|
||||
<li>Site de gestion de formation dans le domaine des sports aquatiques</li>
|
||||
<li>Conseil divers en informatique</li>
|
||||
<li>Intervention en soutien pour maintenance d’infrastructure</li>
|
||||
<li>intervention pour migration de sites</li>
|
||||
<li>Site vitrine d’entreprise d’enfouissement électrique et gaz</li>
|
||||
<li>Mise en place de scrapping pour entreprise</li>
|
||||
</ul>
|
||||
<h4>Compétences technique : </h4>
|
||||
<ul class="cv_content_competence">
|
||||
<li>Python</li>
|
||||
<li>Django</li>
|
||||
<li>Django REST FRAMEWORK</li>
|
||||
<li>Django Channels</li>
|
||||
<li>Celery</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Redis</li>
|
||||
<li>VueJS</li>
|
||||
<li>NuxtJS</li>
|
||||
<li>Vuetify</li>
|
||||
<li>Docker</li>
|
||||
<li>Docker compose</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<header>
|
||||
<div>
|
||||
<p>Octobre 2021 à Novembre 2023</p>
|
||||
</div>
|
||||
<div class="cv_header_title">
|
||||
<h2>Lead Développeur</h2>
|
||||
<h3>PredicVision - Bordeaux - Full remote</h3>
|
||||
</div>
|
||||
</header>
|
||||
<div class="cv_content">
|
||||
<h4>Objectif : </h4>
|
||||
<p>
|
||||
Création d’une plateforme afin d’analyser et maintenir un parc de bornes de rechargement pour véhicules électriques.
|
||||
</p>
|
||||
<h4>Compétences Techniques : </h4>
|
||||
<ul class="cv_content_competence">
|
||||
<li>Python</li>
|
||||
<li>Django</li>
|
||||
<li>Django REST FRAMEWORK</li>
|
||||
<li>Django Channels</li>
|
||||
<li>Celery</li>
|
||||
<li>OCPP</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Redis</li>
|
||||
<li>VueJS</li>
|
||||
<li>NuxtJS</li>
|
||||
<li>Vuetify</li>
|
||||
<li>Docker</li>
|
||||
<li>Docker compose</li>
|
||||
</ul>
|
||||
<h4>Compétences supplémentaires : </h4>
|
||||
<ul>
|
||||
<li>Gitlab</li>
|
||||
<li>Kanban</li>
|
||||
<li>conseil</li>
|
||||
<li>réunion</li>
|
||||
<li>veille technique</li>
|
||||
<li>étude de faisabilité</li>
|
||||
<li>présentation</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
<article>
|
||||
<header>
|
||||
<div>
|
||||
<p>Septembre 2020 à Octobre 2021</p>
|
||||
</div>
|
||||
<div class="cv_header_title">
|
||||
<h2>Développeur Django</h2>
|
||||
<h3>FMC Production - Nantes - Hybride</h3>
|
||||
</div>
|
||||
</header>
|
||||
<div class="cv_content">
|
||||
<h4>Objectif :</h4>
|
||||
<p>
|
||||
Maintient d’une plateforme e-learning et création d’une plateforme de conférence live et VOD pour le monde médical
|
||||
</p>
|
||||
<h4>Compétences Techniques : </h4>
|
||||
<ul class="cv_content_competence">
|
||||
<li>Python</li>
|
||||
<li>Django</li>
|
||||
<li>Django REST FRAMEWORK</li>
|
||||
<li>Django Channels</li>
|
||||
<li>Celery</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Redis</li>
|
||||
<li>Docker</li>
|
||||
<li>Docker compose</li>
|
||||
</ul>
|
||||
<h4>Compétences supplémentaires : </h4>
|
||||
<ul>
|
||||
<li>Gitlab</li>
|
||||
<li>Daily</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
17
home/templates/home/index.html
Normal file
17
home/templates/home/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
{% extends "home/layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Présentation</h2>
|
||||
<p>
|
||||
Développeur passionné d'informatique depuis le plus jeune âge.
|
||||
Cette page sera un condenser d'une vitrine de moi même,
|
||||
des prestations que je peux offrir
|
||||
ainsi qu'une multitude de partage de connaissance à travers mon blog
|
||||
</p>
|
||||
<h2>En quête d'un projet sur la DATA</h2>
|
||||
<p>
|
||||
Ayant une expérience dans la data,
|
||||
je suis en recherche d'un projet qui approfondira mes connaissances sur le sujet
|
||||
avec pourquoi pas un cumul de connaissances sur le web
|
||||
</p>
|
||||
{% endblock %}
|
||||
1
home/templates/home/layout.html
Normal file
1
home/templates/home/layout.html
Normal file
@@ -0,0 +1 @@
|
||||
{% extends "base.html" %}
|
||||
19
home/templates/home/service.html
Normal file
19
home/templates/home/service.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends "home/layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p>
|
||||
Prestation de service ou de maintenance Python/Django/Flask/FastAPI :
|
||||
facturation à la journée à raison de 450€ par jour en moyenne (ce prix peut varier si c'est un projet qui dur dans le temps)
|
||||
</p>
|
||||
<ul>
|
||||
<li>Création de script</li>
|
||||
<li>Création de site simple comme complex</li>
|
||||
<li>Création de graphique (chart) sur des grands comme des petits volumes de donnée</li>
|
||||
<li>Mise à jour de sécurité</li>
|
||||
<li>Correction de bug</li>
|
||||
<li>Modification de votre projet existant</li>
|
||||
<li>Migration</li>
|
||||
<li>Optimisation</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
3
home/tests.py
Normal file
3
home/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
14
home/urls.py
Normal file
14
home/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
app_name = 'home'
|
||||
urlpatterns = [
|
||||
path('', views.IndexView.as_view(), name='index'),
|
||||
path("cv/", views.CVView.as_view(), name='cv'),
|
||||
path("service/", views.ServiceView.as_view(), name='service'),
|
||||
path("contact/", views.ContactView.as_view(), name='contact'),
|
||||
path("contact_valid/", views.ContactValidView.as_view(), name='contact_valid'),
|
||||
path("hit/", views.hit, name="hit")
|
||||
]
|
||||
50
home/views.py
Normal file
50
home/views.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from django.views.generic import TemplateView, FormView
|
||||
from django.http import HttpResponse
|
||||
|
||||
from ipware import get_client_ip
|
||||
from urllib import parse
|
||||
|
||||
from .forms import ContactForm
|
||||
from .models import Hit
|
||||
|
||||
|
||||
class IndexView(TemplateView):
|
||||
template_name = 'home/index.html'
|
||||
|
||||
|
||||
class CVView(TemplateView):
|
||||
template_name = 'home/cv.html'
|
||||
|
||||
|
||||
class ServiceView(TemplateView):
|
||||
template_name = "home/service.html"
|
||||
|
||||
|
||||
class ContactView(FormView):
|
||||
template_name = 'home/contact.html'
|
||||
form_class = ContactForm
|
||||
|
||||
|
||||
class ContactValidView(TemplateView):
|
||||
template_name = "home/contact_success.html"
|
||||
|
||||
|
||||
def hit(request):
|
||||
if request.method == "POST":
|
||||
ip_address, _ = get_client_ip(request)
|
||||
ip_address = '.'.join(ip_address.split('.')[:-1]+["0"])
|
||||
path_parser = parse.urlsplit(request.headers["referer"])
|
||||
path = path_parser.path
|
||||
parameters = dict(parse.parse_qsl(path_parser.query))
|
||||
user_agent = request.headers["user-agent"]
|
||||
session_id = request.session.session_key
|
||||
|
||||
Hit.objects.create(
|
||||
path=path_parser.path,
|
||||
parameters=parameters,
|
||||
session_id=session_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
)
|
||||
return HttpResponse("")
|
||||
|
||||
22
manage.py
Normal file
22
manage.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
3
pytest.ini
Normal file
3
pytest.ini
Normal file
@@ -0,0 +1,3 @@
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = app.settings
|
||||
python_files = test_*
|
||||
10
requirements/common.txt
Normal file
10
requirements/common.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
asgiref==3.8.*
|
||||
Django==5.0.*
|
||||
django-cors-headers==4.4.*
|
||||
sqlparse==0.5.*
|
||||
tzdata==2024.1
|
||||
django-vite==3.0.*
|
||||
python-dotenv==1.0.*
|
||||
django-ordered-model==3.7.*
|
||||
django-tinymce==4.1.*
|
||||
django-ipware==7.0.*
|
||||
2
requirements/dev.txt
Normal file
2
requirements/dev.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
-r common.txt
|
||||
pytest-django==4.8.*
|
||||
1
requirements/prod.txt
Normal file
1
requirements/prod.txt
Normal file
@@ -0,0 +1 @@
|
||||
-r common.txt
|
||||
45
templates/base.html
Normal file
45
templates/base.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% load django_vite %}
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{% block base_title %}devpanel - {% block title %}Accueil{% endblock %}{% endblock %}</title>
|
||||
{% block base_css %}
|
||||
<link rel="stylesheet" href="{% vite_asset_url "styles/app.css" %}">
|
||||
{% block css %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header id="main_header">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="{% url "home:index" %}">Présentation</a></li>
|
||||
<li><a href="{% url "home:cv" %}">CV</a></li>
|
||||
<li><a href="{% url "home:service" %}">Services</a></li>
|
||||
<li><a href="{% url "blog:article_list" %}">Blog (WIP)</a></li>
|
||||
<li><a href="{% url "home:contact" %}">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="main_app">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer id="main_footer">
|
||||
</footer>
|
||||
|
||||
{% block base_js %}
|
||||
{% vite_asset "application/app.js" %}
|
||||
|
||||
{% block js %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/blog/__init__.py
Normal file
0
tests/blog/__init__.py
Normal file
0
tests/blog/test_models.py
Normal file
0
tests/blog/test_models.py
Normal file
0
tests/blog/test_views.py
Normal file
0
tests/blog/test_views.py
Normal file
0
tests/home/__init__.py
Normal file
0
tests/home/__init__.py
Normal file
51
tests/home/test_views.py
Normal file
51
tests/home/test_views.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from django.test import Client
|
||||
from django.urls import reverse
|
||||
import pytest
|
||||
from pytest_django import asserts
|
||||
|
||||
|
||||
def test_index():
|
||||
c = Client()
|
||||
response = c.get(reverse('home:index'))
|
||||
|
||||
assert response.status_code == 200
|
||||
asserts.assertTemplateUsed(response, 'home/index.html')
|
||||
|
||||
|
||||
def test_cv():
|
||||
c = Client()
|
||||
response = c.get(reverse('home:cv'))
|
||||
|
||||
assert response.status_code == 200
|
||||
asserts.assertTemplateUsed(response, 'home/cv.html')
|
||||
|
||||
|
||||
def test_service():
|
||||
c = Client()
|
||||
response = c.get(reverse('home:service'))
|
||||
|
||||
assert response.status_code == 200
|
||||
asserts.assertTemplateUsed(response, 'home/service.html')
|
||||
|
||||
|
||||
def test_contact():
|
||||
c = Client()
|
||||
response = c.get(reverse('home:contact'))
|
||||
|
||||
assert response.status_code == 200
|
||||
asserts.assertTemplateUsed(response, 'home/contact.html')
|
||||
|
||||
|
||||
def test_contact_submit_valid():
|
||||
c = Client()
|
||||
data = {"full_name": "foo bar", "email": "foo@bar.com", "subject": "test", "message": "test"}
|
||||
response = c.post(reverse('home:contact'), data=data)
|
||||
asserts.assertRedirects(response, reverse('home:contact_valid'))
|
||||
|
||||
|
||||
def test_contact_submit_invalid():
|
||||
c = Client()
|
||||
data = {"bad": "form"}
|
||||
response = c.post(reverse('home:contact'), data=data)
|
||||
assert response.status_code == 200
|
||||
|
||||
0
tests/user/__init__.py
Normal file
0
tests/user/__init__.py
Normal file
0
user/__init__.py
Normal file
0
user/__init__.py
Normal file
27
user/admin.py
Normal file
27
user/admin.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
|
||||
from .models import User
|
||||
from .forms import UserCreationForm, UserChangeForm
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
add_form = UserCreationForm
|
||||
form = UserChangeForm
|
||||
model = User
|
||||
list_display = ["email", "is_staff", "is_superuser", "is_active"]
|
||||
list_filter = ["email", "is_staff", "is_superuser", "is_active"]
|
||||
fieldsets = [
|
||||
[None, {"fields": ["email", "password"]}],
|
||||
("permissions", {"fields": ["is_staff", "is_active", "is_superuser"]})
|
||||
]
|
||||
add_fieldsets = [
|
||||
[None, {
|
||||
"classes": ["wide"],
|
||||
"fields": ["email", "password1", "password2", "is_staff", "is_active", "is_superuser"]
|
||||
}]
|
||||
]
|
||||
search_fields = ["email"]
|
||||
ordering = ["email"]
|
||||
|
||||
6
user/apps.py
Normal file
6
user/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UserConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'user'
|
||||
11
user/forms.py
Normal file
11
user/forms.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.contrib.auth import forms as BaseAuthForms
|
||||
|
||||
from .models import User
|
||||
|
||||
|
||||
class UserCreationForm(BaseAuthForms.UserCreationForm):
|
||||
pass
|
||||
|
||||
|
||||
class UserChangeForm(BaseAuthForms.UserChangeForm):
|
||||
pass
|
||||
44
user/migrations/0001_initial.py
Normal file
44
user/migrations/0001_initial.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# Generated by Django 5.0.7 on 2024-07-19 08:31
|
||||
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
import user.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='User',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'abstract': False,
|
||||
},
|
||||
managers=[
|
||||
('objects', user.models.UsernameUserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
user/migrations/__init__.py
Normal file
0
user/migrations/__init__.py
Normal file
71
user/models.py
Normal file
71
user/models.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
||||
|
||||
|
||||
# class EmailUserManager(BaseUserManager):
|
||||
# use_in_migrations = True
|
||||
#
|
||||
# def _create_user(self, email, password, **extra_fields):
|
||||
# if not email:
|
||||
# raise ValueError("Un email doit être défini")
|
||||
#
|
||||
# email = self.normalize_email(email)
|
||||
# user = self.model(email=email, **extra_fields)
|
||||
# user.set_password(password)
|
||||
# user.save(using=self.db)
|
||||
# return user
|
||||
#
|
||||
# def create_user(self, username, email, password=None, **extra_fields):
|
||||
# extra_fields.setdefault("is_staff", False)
|
||||
# extra_fields.setdefault("is_superuser", False)
|
||||
# return self._create_user(username, email, password, **extra_fields)
|
||||
#
|
||||
# def create_superuser(self, email, password, **extra_fields):
|
||||
# extra_fields.setdefault("is_staff", True)
|
||||
# extra_fields.setdefault("is_superuser", True)
|
||||
#
|
||||
# if extra_fields.get("is_staff") is not True:
|
||||
# raise ValueError("Superuser doit être staff à True")
|
||||
# if extra_fields.get("is_superuser") is not True:
|
||||
# raise ValueError("SuperUser doit être is_superuser à True")
|
||||
#
|
||||
# return self._create_user(email, password, **extra_fields)
|
||||
|
||||
|
||||
class UsernameUserManager(BaseUserManager):
|
||||
use_in_migrations = True
|
||||
|
||||
def _create_user(self, username, email, password, **extra_fields):
|
||||
if not username:
|
||||
raise ValueError("Un username doit être défini")
|
||||
if not email:
|
||||
raise ValueError("Un email doit être défini")
|
||||
|
||||
email = self.normalize_email(email)
|
||||
user = self.model(username=username, email=email, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self.db)
|
||||
return user
|
||||
|
||||
def create_user(self, username, email, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", False)
|
||||
extra_fields.setdefault("is_superuser", False)
|
||||
return self._create_user(username, email, password, **extra_fields)
|
||||
|
||||
def create_superuser(self, username, email, password, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
|
||||
if extra_fields.get("is_staff") is not True:
|
||||
raise ValueError("Superuser doit être staff à True")
|
||||
if extra_fields.get("is_superuser") is not True:
|
||||
raise ValueError("SuperUser doit être is_superuser à True")
|
||||
|
||||
return self._create_user(username, email, password, **extra_fields)
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
email = models.EmailField(unique=True)
|
||||
|
||||
REQUIRED_FIELDS = []
|
||||
objects = UsernameUserManager()
|
||||
3
user/tests.py
Normal file
3
user/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
user/views.py
Normal file
3
user/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Reference in New Issue
Block a user