wip
This commit is contained in:
parent
34f1de698f
commit
c742b244b4
15
config/api_urls.py
Normal file
15
config/api_urls.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from django.conf.urls import include
|
||||||
|
from django.urls import re_path
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from grids import views as grids_views
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r"levels", grids_views.LevelViewSet, basename="level")
|
||||||
|
router.register(r"grids", grids_views.GridViewSet, basename="grid")
|
||||||
|
router.register(r"words", grids_views.WordViewSet, basename="word")
|
||||||
|
router.register(r"placement", grids_views.PlacementViewSet, basename="placement")
|
||||||
|
|
||||||
|
|
||||||
|
v1_pattern = router.urls
|
||||||
|
urlpatterns = [re_path(r"^v1/", include((v1_pattern, "v1"), namespace="v1"))]
|
|
@ -50,6 +50,7 @@ INSTALLED_APPS = [
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
"django.middleware.common.CommonMiddleware",
|
"django.middleware.common.CommonMiddleware",
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
@ -132,3 +133,19 @@ STATIC_URL = "static/"
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
|
||||||
|
# Origins
|
||||||
|
if DEBUG:
|
||||||
|
CORS_ALLOW_ALL_ORIGINS = True
|
||||||
|
|
||||||
|
|
||||||
|
# Rest framework
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
# "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
|
||||||
|
"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
|
||||||
|
"DEFAULT_PARSER_CLASSES": (
|
||||||
|
"rest_framework.parsers.JSONParser",
|
||||||
|
"rest_framework.parsers.FormParser",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
|
@ -15,9 +15,10 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include, re_path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("users/", include("users.urls", namespace="users")),
|
path("users/", include("users.urls", namespace="users")),
|
||||||
|
re_path(r"^api/", include(("config.api_urls", "api"), namespace="api")),
|
||||||
]
|
]
|
||||||
|
|
103
frontend/package-lock.json
generated
103
frontend/package-lock.json
generated
|
@ -8,8 +8,10 @@
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||||
"@sveltejs/adapter-auto": "^2.1.0",
|
"@sveltejs/adapter-auto": "^2.1.0",
|
||||||
"@sveltejs/kit": "^1.24.1"
|
"@sveltejs/kit": "^1.24.1",
|
||||||
|
"axios": "^1.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
"@sveltejs/vite-plugin-svelte": "^2.4.2",
|
||||||
|
@ -378,6 +380,15 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fortawesome/fontawesome-free": {
|
||||||
|
"version": "6.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz",
|
||||||
|
"integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||||
|
@ -606,6 +617,11 @@
|
||||||
"dequal": "^2.0.3"
|
"dequal": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.15",
|
"version": "10.4.15",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz",
|
||||||
|
@ -643,6 +659,16 @@
|
||||||
"postcss": "^8.1.0"
|
"postcss": "^8.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.0",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
|
||||||
|
@ -817,6 +843,17 @@
|
||||||
"periscopic": "^3.1.0"
|
"periscopic": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||||
|
@ -888,6 +925,14 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dequal": {
|
"node_modules/dequal": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||||
|
@ -1029,6 +1074,38 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||||
|
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.3.6",
|
"version": "4.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
|
||||||
|
@ -1316,6 +1393,25 @@
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/min-indent": {
|
"node_modules/min-indent": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||||
|
@ -1660,6 +1756,11 @@
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
|
|
|
@ -22,7 +22,9 @@
|
||||||
"vite": "^4.4.5"
|
"vite": "^4.4.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.4.2",
|
||||||
"@sveltejs/adapter-auto": "^2.1.0",
|
"@sveltejs/adapter-auto": "^2.1.0",
|
||||||
"@sveltejs/kit": "^1.24.1"
|
"@sveltejs/kit": "^1.24.1",
|
||||||
|
"axios": "^1.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
frontend/src/lib/LevelTag.svelte
Normal file
25
frontend/src/lib/LevelTag.svelte
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Level } from "./types";
|
||||||
|
|
||||||
|
export let level: Level;
|
||||||
|
let classes: string;
|
||||||
|
|
||||||
|
$: switch (level.levelNumber) {
|
||||||
|
case 1: {
|
||||||
|
classes = "bg-lime-500";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
classes = "bg-blue-500";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
classes = "bg-red-500";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="text-sm font-bold text-white px-2 rounded-md {classes}"
|
||||||
|
>{level.levelString}</span
|
||||||
|
>
|
9
frontend/src/lib/NavLink.svelte
Normal file
9
frontend/src/lib/NavLink.svelte
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
export let title: string;
|
||||||
|
export let href: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<a {href} class:font-bold={$page.url.pathname == href}>
|
||||||
|
{title}
|
||||||
|
</a>
|
14
frontend/src/lib/WordForm.svelte
Normal file
14
frontend/src/lib/WordForm.svelte
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Level } from "./types";
|
||||||
|
import LevelTag from "./LevelTag.svelte";
|
||||||
|
|
||||||
|
export let word: string;
|
||||||
|
export let definition: string;
|
||||||
|
export let level: Level;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<LevelTag {level} />
|
||||||
|
</div>
|
||||||
|
<div>{word}</div>
|
||||||
|
<div>{definition}</div>
|
8
frontend/src/lib/api.ts
Normal file
8
frontend/src/lib/api.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import axios from "axios";
|
||||||
|
import { PUBLIC_API_ENDPOINT } from "$env/static/public";
|
||||||
|
|
||||||
|
const axiosAPI = axios.create({
|
||||||
|
baseURL: PUBLIC_API_ENDPOINT
|
||||||
|
});
|
||||||
|
|
||||||
|
export default axiosAPI;
|
59
frontend/src/lib/case.ts
Normal file
59
frontend/src/lib/case.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// camelCase to snake_case
|
||||||
|
export function camelToSnakeCase(o: any): any {
|
||||||
|
if (o === null || typeof o != "object") {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(o)) {
|
||||||
|
return o.map(camelToSnakeCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
const build: { [key: string]: string } = {};
|
||||||
|
for (const key in o) {
|
||||||
|
const newKey = toSnake(key);
|
||||||
|
|
||||||
|
let value = o[key];
|
||||||
|
|
||||||
|
if (typeof value === "object") {
|
||||||
|
value = camelToSnakeCase(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
build[newKey] = value;
|
||||||
|
}
|
||||||
|
return build;
|
||||||
|
}
|
||||||
|
|
||||||
|
// snake_case to camelCase
|
||||||
|
export function snakeToCamelCase(o: any): any {
|
||||||
|
if (o === null || typeof o != "object") {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(o)) {
|
||||||
|
return o.map(snakeToCamelCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
const build: { [key: string]: string } = {};
|
||||||
|
for (const key in o) {
|
||||||
|
const newKey = toCamel(key);
|
||||||
|
|
||||||
|
let value = o[key];
|
||||||
|
|
||||||
|
if (typeof value === "object") {
|
||||||
|
value = snakeToCamelCase(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
build[newKey] = value;
|
||||||
|
}
|
||||||
|
return build;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toCamel(s: string): string {
|
||||||
|
return s.replace(/([-_][a-z])/gi, ($1) => {
|
||||||
|
return $1.toUpperCase().replace("-", "").replace("_", "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSnake(s: string): string {
|
||||||
|
return s.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
||||||
|
}
|
13
frontend/src/lib/types.ts
Normal file
13
frontend/src/lib/types.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
export interface Level {
|
||||||
|
id: number;
|
||||||
|
levelNumber: number;
|
||||||
|
levelString: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Word {
|
||||||
|
id: number;
|
||||||
|
word: string;
|
||||||
|
definition: string;
|
||||||
|
level: Level;
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import "../app.css";
|
import "../app.css";
|
||||||
import UserNavbar from "$lib/UserNavbar.svelte";
|
import UserNavbar from "$lib/UserNavbar.svelte";
|
||||||
|
import NavLink from "../lib/NavLink.svelte";
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
import "@fortawesome/fontawesome-free/css/all.min.css";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -13,7 +16,9 @@
|
||||||
<nav class="p-2 md:p-4 border-b flex flex-row justify-between">
|
<nav class="p-2 md:p-4 border-b flex flex-row justify-between">
|
||||||
<div><a href="/" class="font-bold">Gerioù-bir</a></div>
|
<div><a href="/" class="font-bold">Gerioù-bir</a></div>
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<a href="/">Kaelioù publik</a>
|
<NavLink href="/kaeliou" title="Kaelioù publik" />
|
||||||
|
<NavLink href="/ma_geriou" title="Ma gerioù" />
|
||||||
|
<NavLink href="/ma_chaeliou" title="Ma c'haelioù" />
|
||||||
<UserNavbar />
|
<UserNavbar />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
7
frontend/src/routes/kaeliou/+page.svelte
Normal file
7
frontend/src/routes/kaeliou/+page.svelte
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{$page.url.pathname}
|
||||||
|
</main>
|
7
frontend/src/routes/ma_chaeliou/+page.svelte
Normal file
7
frontend/src/routes/ma_chaeliou/+page.svelte
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{$page.url.pathname}
|
||||||
|
</main>
|
81
frontend/src/routes/ma_geriou/+page.svelte
Normal file
81
frontend/src/routes/ma_geriou/+page.svelte
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
import axios from "$lib/api.ts";
|
||||||
|
import WordForm from "$lib/WordForm.svelte";
|
||||||
|
import { type Word } from "../../lib/types";
|
||||||
|
|
||||||
|
import { snakeToCamelCase } from "$lib/case";
|
||||||
|
|
||||||
|
let words: Word[];
|
||||||
|
const wordResponse = axios.get("words/").then((data) => {
|
||||||
|
words = snakeToCamelCase(data.data);
|
||||||
|
});
|
||||||
|
let isFormNewShown = false;
|
||||||
|
|
||||||
|
function toggleFormNew() {
|
||||||
|
isFormNewShown = !isFormNewShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newWord(e) {
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
|
||||||
|
const data = {};
|
||||||
|
for (let field of formData) {
|
||||||
|
const [key, value] = field;
|
||||||
|
data[key] = value;
|
||||||
|
}
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{#await wordResponse}
|
||||||
|
<p>…</p>
|
||||||
|
{:then}
|
||||||
|
<div class="grid grid-cols-3 gap-y-2 gap-x-1">
|
||||||
|
<div class="font-bold">Live</div>
|
||||||
|
<div class="font-bold">Ger</div>
|
||||||
|
<div class="font-bold">Termenadur</div>
|
||||||
|
|
||||||
|
{#if !isFormNewShown}
|
||||||
|
<div />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="bg-violet-600 hover:bg-violet-700 rounded-md text-white p-1 w-8 text-center"
|
||||||
|
on:click={toggleFormNew}
|
||||||
|
>
|
||||||
|
<span class="fa-regular fa-plus" />
|
||||||
|
</button>
|
||||||
|
<div />
|
||||||
|
{:else}
|
||||||
|
<form on:submit|preventDefault={newWord}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
name="level"
|
||||||
|
placeholder="Live"
|
||||||
|
class="p-1 rounded-md border w-1/2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
name="word"
|
||||||
|
placeholder="Ger"
|
||||||
|
class="p-1 rounded-md border w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
name="definition"
|
||||||
|
placeholder="Termenadur"
|
||||||
|
class="p-1 rounded-md border w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each words as word}
|
||||||
|
<WordForm {...word} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</main>
|
|
@ -1,3 +1,7 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Word, Grid, Placement, Level
|
||||||
|
|
||||||
# Register your models here.
|
admin.site.register(Level)
|
||||||
|
admin.site.register(Word)
|
||||||
|
admin.site.register(Grid)
|
||||||
|
admin.site.register(Placement)
|
||||||
|
|
114
grids/migrations/0001_initial.py
Normal file
114
grids/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-09-12 12:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Grid",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Level",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("level_numer", models.IntegerField()),
|
||||||
|
("string_number", models.CharField(max_length=50)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Word",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("word", models.CharField(max_length=50)),
|
||||||
|
("definition", models.CharField(max_length=255)),
|
||||||
|
(
|
||||||
|
"level",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="words",
|
||||||
|
to="grids.level",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Placement",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("coordinates_first_letter", models.CharField(max_length=10)),
|
||||||
|
(
|
||||||
|
"direction",
|
||||||
|
models.SmallIntegerField(
|
||||||
|
choices=[(1, "Right"), (2, "Bottom")], default=1
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"grid",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="grids.grid"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"word",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="grids.word"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="grid",
|
||||||
|
name="level",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="grids",
|
||||||
|
to="grids.level",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="grid",
|
||||||
|
name="words",
|
||||||
|
field=models.ManyToManyField(through="grids.Placement", to="grids.word"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-09-12 12:56
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("grids", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="level",
|
||||||
|
old_name="level_numer",
|
||||||
|
new_name="level_number",
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-09-12 12:56
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("grids", "0002_rename_level_numer_level_level_number"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="level",
|
||||||
|
old_name="string_number",
|
||||||
|
new_name="level_string",
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,32 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
|
class Level(models.Model):
|
||||||
|
level_number = models.IntegerField()
|
||||||
|
level_string = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
|
||||||
|
class Word(models.Model):
|
||||||
|
word = models.CharField(max_length=50)
|
||||||
|
definition = models.CharField(max_length=255)
|
||||||
|
level = models.ForeignKey(Level, on_delete=models.CASCADE, related_name="words")
|
||||||
|
|
||||||
|
|
||||||
|
class Grid(models.Model):
|
||||||
|
level = models.ForeignKey(Level, on_delete=models.CASCADE, related_name="grids")
|
||||||
|
words = models.ManyToManyField(Word, through="Placement")
|
||||||
|
|
||||||
|
|
||||||
|
class Direction:
|
||||||
|
RIGHT = 1
|
||||||
|
BOTTOM = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Placement(models.Model):
|
||||||
|
word = models.ForeignKey(Word, on_delete=models.CASCADE)
|
||||||
|
grid = models.ForeignKey(Grid, on_delete=models.CASCADE)
|
||||||
|
coordinates_first_letter = models.CharField(max_length=10)
|
||||||
|
direction = models.SmallIntegerField(
|
||||||
|
default=Direction.RIGHT,
|
||||||
|
choices=[(Direction.RIGHT, "Right"), (Direction.BOTTOM, "Bottom")],
|
||||||
|
)
|
||||||
|
|
28
grids/serializers.py
Normal file
28
grids/serializers.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Word, Grid, Placement, Level
|
||||||
|
|
||||||
|
|
||||||
|
class LevelSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Level
|
||||||
|
fields = ["id", "level_number", "level_string"]
|
||||||
|
|
||||||
|
|
||||||
|
class WordSerializer(serializers.ModelSerializer):
|
||||||
|
level = LevelSerializer(many=False, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Word
|
||||||
|
fields = ["id", "word", "definition", "level"]
|
||||||
|
|
||||||
|
|
||||||
|
class GridSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Grid
|
||||||
|
fields = ["id", "words"]
|
||||||
|
|
||||||
|
|
||||||
|
class PlacementSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Placement
|
||||||
|
fields = ["id", "word", "grid", "coordinates_first_letter", "direction"]
|
|
@ -1,3 +1,33 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from rest_framework import pagination, status, viewsets
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
# Create your views here.
|
from .models import Word, Grid, Placement, Level
|
||||||
|
|
||||||
|
from .serializers import (
|
||||||
|
WordSerializer,
|
||||||
|
GridSerializer,
|
||||||
|
PlacementSerializer,
|
||||||
|
LevelSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LevelViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Level.objects.all()
|
||||||
|
serializer_class = LevelSerializer
|
||||||
|
ordering_fields = ["level_number"]
|
||||||
|
|
||||||
|
|
||||||
|
class WordViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Word.objects.all()
|
||||||
|
serializer_class = WordSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class GridViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Grid.objects.all()
|
||||||
|
serializer_class = GridSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PlacementViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Placement.objects.all()
|
||||||
|
serializer_class = PlacementSerializer
|
||||||
|
|
16
poetry.lock
generated
16
poetry.lock
generated
|
@ -34,6 +34,20 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
bcrypt = ["bcrypt"]
|
bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-cors-headers"
|
||||||
|
version = "4.2.0"
|
||||||
|
description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "django_cors_headers-4.2.0-py3-none-any.whl", hash = "sha256:9ada212b0e2efd4a5e339360ffc869cb21ac5605e810afe69f7308e577ea5bde"},
|
||||||
|
{file = "django_cors_headers-4.2.0.tar.gz", hash = "sha256:f9749c6410fe738278bc2b6ef17f05195bc7b251693c035752d8257026af024f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
Django = ">=3.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-environ"
|
name = "django-environ"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -106,4 +120,4 @@ files = [
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "d592e1d1d4d9e64b28486f934462658ca397646908507648190a2711704eb769"
|
content-hash = "892a80c189815eb735e7fc36932e4bb9f534ba248bfa6e6af37ca882fec04f14"
|
||||||
|
|
|
@ -11,6 +11,7 @@ python = "^3.11"
|
||||||
django = "^4.2.5"
|
django = "^4.2.5"
|
||||||
django-environ = "^0.11.2"
|
django-environ = "^0.11.2"
|
||||||
djangorestframework = "^3.14.0"
|
djangorestframework = "^3.14.0"
|
||||||
|
django-cors-headers = "^4.2.0"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
Loading…
Reference in a new issue