Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: ["3.10"]
python-version: ["3.13"]

steps:
- uses: actions/checkout@v2
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# my-elearning

A Django-based e-learning platform built with Python 3.
A Django 6-based e-learning platform built for Python 3.13.

[![Django CI](https://github.com/delitamakanda/elearning/actions/workflows/django.yml/badge.svg?branch=master)](https://github.com/delitamakanda/elearning/actions/workflows/django.yml)

Expand All @@ -14,6 +14,7 @@ A Django-based e-learning platform built with Python 3.

### Installation
```bash
pip install --upgrade pip
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
Expand Down
84 changes: 84 additions & 0 deletions apps/common/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
Custom taggit serializer field and mixin to replace the abandoned
django-taggit-serializer package.

Usage
-----
from apps.common.serializers import TaggitSerializer, TagListSerializerField

class MySerializer(TaggitSerializer, serializers.ModelSerializer):
tags = TagListSerializerField()

class Meta:
model = MyModel
fields = ('id', 'tags', ...)
"""

from rest_framework import serializers


class TagListSerializerField(serializers.Field):
"""Serializes a django-taggit ``TaggableManager`` as a flat list of tag names."""

child = serializers.CharField()

def __init__(self, *args, **kwargs):
kwargs.setdefault("default", list)
super().__init__(*args, **kwargs)

def to_internal_value(self, data):
if isinstance(data, str):
data = [tag.strip() for tag in data.split(",") if tag.strip()]
if not isinstance(data, list):
raise serializers.ValidationError("Expected a list of tag strings.")
errors = []
for item in data:
try:
self.child.run_validation(item)
except serializers.ValidationError as e:
errors.append(e.detail)
else:
errors.append({})
if any(errors):
raise serializers.ValidationError(errors)
return data

def to_representation(self, value):
if not value:
return []
if hasattr(value, "all"):
return [tag.name for tag in value.all()]
return list(value)


class TaggitSerializer(serializers.Serializer):
"""
Mixin for ModelSerializer classes that include one or more
``TagListSerializerField`` fields backed by a ``TaggableManager``.

Handles saving tags after the instance is created or updated.
"""

def _get_tag_fields(self):
return {
field_name: field
for field_name, field in self.fields.items()
if isinstance(field, TagListSerializerField)
}

def create(self, validated_data):
tag_fields = self._get_tag_fields()
tag_data = {name: validated_data.pop(name, []) for name in tag_fields}
instance = super().create(validated_data)
for field_name, tags in tag_data.items():
getattr(instance, field_name).set(tags)
return instance

def update(self, instance, validated_data):
tag_fields = self._get_tag_fields()
tag_data = {name: validated_data.pop(name, None) for name in tag_fields}
instance = super().update(instance, validated_data)
for field_name, tags in tag_data.items():
if tags is not None:
getattr(instance, field_name).set(tags)
return instance
63 changes: 21 additions & 42 deletions myelearning/settings.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
"""
Django settings for myelearning project.
"""Django settings for myelearning project."""

Generated by 'django-admin startproject' using Django 1.11.5.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os
from pathlib import Path
import sys
from decouple import config
from django.urls import reverse_lazy

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BASE_DIR = Path(__file__).resolve().parent.parent

# Add the parent directory to sys.path
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, PROJECT_ROOT)
PROJECT_ROOT = BASE_DIR
sys.path.insert(0, str(PROJECT_ROOT))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY', 'dummy_secret_key')
Expand Down Expand Up @@ -56,12 +43,12 @@
'widget_tweaks',
'corsheaders',
'taggit',
'taggit_serializer',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'apps.students.middleware.SessionTimeoutMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
Expand All @@ -73,7 +60,6 @@
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'django.contrib.admindocs.middleware.XViewMiddleware',
'corsheaders.middleware.CorsMiddleware',
]

# if DEBUG == False:
Expand All @@ -87,7 +73,7 @@
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
# template at the root of the project
os.path.join(BASE_DIR, 'templates'),
BASE_DIR / 'templates',
],
'APP_DIRS': True,
'OPTIONS': {
Expand All @@ -105,20 +91,14 @@

SITE_ID = 1

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'NAME': BASE_DIR / 'db.sqlite3',
}
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
Expand All @@ -135,33 +115,27 @@
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = False
USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
MEDIA_URL = '/media/'

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATIC_ROOT = BASE_DIR / 'staticfiles'
MEDIA_ROOT = BASE_DIR / 'media'

STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
BASE_DIR / 'static',
)

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace removed STATICFILES_STORAGE in Django 6

The settings now declare STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage', but requirements pin the project to Django >=6.0a1. Django 6 removes the legacy STATICFILES_STORAGE/DEFAULT_FILE_STORAGE settings in favor of the STORAGES mapping, so keeping this line causes startup/staticfiles initialization to fail and prevents WhiteNoise from being used. Configure the STORAGES['staticfiles'] backend instead to keep the app running on the new Django version.

Useful? React with 👍 / 👎.


# Custom auth

AUTH_USER_MODEL = 'students.User'
Expand Down Expand Up @@ -215,25 +189,30 @@
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True

CORS_ORIGIN_WHITELIST = (
CORS_ALLOWED_ORIGINS = [
'https://myelearning.herokuapp.com',
'http://localhost:8080',
'http://localhost:8100',
'http://localhost:8000',
'http://localhost:3000',
'https://pwa-myelearning.netlify.app',
'http://localhost',
)
]
CORS_ALLOW_ALL_ORIGINS = False

CORS_ALLOW_METHODS = (
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS',
)

# Task async
CELERY_BROKER_URL = config('REDIS_URL', 'redis://localhost:6379/0', cast=str)
CELERY_RESULT_BACKEND = config('REDIS_URL', 'redis://localhost:6379/0', cast=str)

CRISPY_TEMPLATE_PACK = 'bootstrap4'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
62 changes: 24 additions & 38 deletions myelearning/urls.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,5 @@
"""myelearning URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.urls import re_path as url, include
"""myelearning URL Configuration for Django 6."""
from django.urls import include, path, re_path
from django.conf import settings
from django.views import generic
from django.conf.urls.static import static
Expand All @@ -24,28 +10,28 @@
from django.views.generic import TemplateView

urlpatterns = [
url(r'^$', generic.RedirectView.as_view(url='/course/', permanent=True)),

url(r'^accounts/login/$', auth_views.LoginView.as_view(), name='login'),
url(r'^accounts/logout/$', auth_views.LogoutView.as_view(), name='logout'),
url(r'^accounts/signup/$', classroom.SignupView.as_view(), name='signup'),
url(r'^password-change/$', auth_views.PasswordChangeView.as_view(), name='password_change'),
url(r'^password-change/done/$', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
url(r'^password-reset/$', auth_views.PasswordResetView.as_view(), name='password_reset'),
url(r'^password-reset/done/$', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
url(r'^password-reset/confirm/(?P<uidb64>[-\w]+)/(?P<token>[-\w]+)/$', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
url(r'^password-reset/complete/$', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
url(r'^admin/', admin.site.urls),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^course/', include(('apps.courses.urls', 'courses'))),
url(r'^students/', include(('apps.students.urls', 'students'))),

url(r'^api/', include(('apps.courses.api.urls', 'api'), namespace='api')),

url(r'^sw.js', (TemplateView.as_view(template_name="service-worker.js", content_type='application/javascript', )), name='sw.js'),
url(r'^offline.html', (TemplateView.as_view(template_name="offline.html")), name='offline.html'),
path('', generic.RedirectView.as_view(url='/course/', permanent=True)),

path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('accounts/signup/', classroom.SignupView.as_view(), name='signup'),
path('password-change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('password-change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
path('password-reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
re_path(r'^password-reset/confirm/(?P<uidb64>[-\w]+)/(?P<token>[-\w]+)/$', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('password-reset/complete/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),

path('admin/', admin.site.urls),
path('admin/doc/', include('django.contrib.admindocs.urls')),

path('course/', include(('apps.courses.urls', 'courses'))),
path('students/', include(('apps.students.urls', 'students'))),

path('api/', include(('apps.courses.api.urls', 'api'), namespace='api')),

path('sw.js', TemplateView.as_view(template_name="service-worker.js", content_type='application/javascript'), name='sw.js'),
path('offline.html', TemplateView.as_view(template_name="offline.html"), name='offline.html'),
]

if settings.DEBUG:
Expand Down
Loading
Loading