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
56 changes: 54 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,54 @@
[tool.isort]
profile = "black"
[build-system]
requires = ["setuptools >= 80.9.0", "setuptools_scm >= 8.3.1"]
build-backend = "setuptools.build_meta"

[project]
name = "timetable-fri"
description = "UL FRI Timetable API"
authors = [{ name = "Gregor Jerše", email = "gregor@jerse.info" }, { name = "Gašper Fele-Žorž", email = "polz@not.si" }]
dynamic = ["version"]
readme = "README.md"
license = "GPL-3.0-or-later"
requires-python = ">=3.13, <3.14"
keywords = ["timetabling", "FRI", "university", "timetable", "api", "django", "rest-framework"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Django :: 5.2",
"Environment :: Console",
"Environment :: Web Environment",
"Intended Audience :: Education",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"Topic :: Software Development :: Libraries :: Python Modules",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"Django ~= 5.2.7",
"djangorestframework ~= 3.16.1",
"django-filter ~= 25.2",
"dj-rest-auth ~= 7.0.1",
"django-cors-headers ~= 4.9.0",
"python-ldap ~= 3.4.5",
"requests ~= 2.32.5",
"django-auth-ldap ~= 5.2.0",
"django-impersonate ~= 1.9.5",
"django-import-export ~= 4.3.12",
"pytz ~= 2025.2",
"icalendar ~= 6.3.1",
"palettable ~= 3.3.3",
]
[project.optional-dependencies]
postgres = ["psycopg[binary] ~= 3.2.12"]
docs = ["sphinx", "sphinx-pyproject"]
package = ["twine", "check-manifest", "build"]
test = ["ruff~=0.14.2", "pytest-cov"]
devel = ["ipython", "types-tqdm"]
[project.urls]
repository = "https://github.com/ul-fri/urnik"
documentation = "https://github.com/ul-fri/urnik"

[tool.setuptools_scm]

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions timetable/admin.py → src/timetable/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class TeacherAdmin(ImportExportActionModelAdmin):
"activities__name",
"activities__short_name",
)
filter_horizontal = ("activities",)
# filter_horizontal = ("activities",)


class ClassroomNResourcesInline(admin.TabularInline):
Expand All @@ -41,7 +41,7 @@ class ClassroomNResourcesInline(admin.TabularInline):


class ClassroomAdmin(ImportExportActionModelAdmin):
filter_horizontal = ("resources",)
# filter_horizontal = ("resources",)
list_filter = ("classroomset",)
inlines = [
ClassroomNResourcesInline,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 2 additions & 1 deletion timetable/models.py → src/timetable/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,8 @@ def default_timetable(request):
- IndexError when no public timetable exists for a given site.
"""
all_timetables = Timetable.objects.all()
current_site = get_current_site(request)
#current_site = get_current_site(request)
current_site = Site.objects.all().last()
Comment on lines +573 to +574

Copilot AI Oct 26, 2025

Copy link

Choose a reason for hiding this comment

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

Using .last() to get the current site is unreliable and will return incorrect results in multi-site deployments. This breaks the original site-aware behavior. If get_current_site(request) is incompatible with the new Django version, consider using Site.objects.get_current() or passing the site_id explicitly rather than defaulting to an arbitrary last site.

Suggested change
#current_site = get_current_site(request)
current_site = Site.objects.all().last()
current_site = get_current_site(request)

Copilot uses AI. Check for mistakes.
current_site_default_timetables = all_timetables.filter(
timetablesite__site=current_site, public=True, timetablesite__default=True
)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
195 changes: 195 additions & 0 deletions src/urnik_fri/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Django settings for urnik project.
import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
from friprosveta.ul_groupname import ULNestedGroupOfNamesType
from django.utils.translation import gettext_lazy as _
from urnik_fri.settings_common import *

# DEBUG = False
DEBUG = True

#--- UNITIME settings ------
UNITIME_DB_USER = "user"
UNITIME_DB_PASSWORD = "password"
UNITIME_DB_NAME = "db_name"
UNITIME_DB_HOST = "db_host"
# -------------------------


ADMINS = (
('polz', 'polz@fri.uni-lj.si'),
('gregor', 'gregor.jerse@fri.uni-lj.si'),
('gregorj', 'gregor@jerse.info'),
('gasperfele@fri1.uni-lj.si', 'polz@fri.uni-lj.si'),
)
MANAGERS = ADMINS

# AUTHENTICATION_BACKENDS = (
# 'guardian.backends.ObjectPermissionBackend',
# 'django.contrib.auth.backends.ModelBackend', # this is default
# )

#-----------------------------------------
# Email settings
#-----------------------------------------
EMAIL_HOST = '10.0.32.2'
EMAIL_PORT = 25
#EMAIL_HOST_USER = 'urnik'
#EMAIL_HOST_PASSWORD = 'pogreglz'
EMAIL_USE_TLS = True


#-----------------------------------------
# Database settings
#-----------------------------------------
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'timetable', # Or path to database file if using sqlite3.
'USER': 'timetable', # Not used with sqlite3.
'PASSWORD': 'qOvpzZODQ4n55JKRyRU16PJBEeUde1uK', # Not used with sqlite3.
'HOST': 'simargl.jerse.info', # Set to empty string for localhost. Not used with sqlite3.
Comment on lines +48 to +51

Copilot AI Oct 26, 2025

Copy link

Choose a reason for hiding this comment

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

Database credentials are hardcoded in the settings file. These sensitive values should be stored in environment variables or a secure configuration management system, not committed to version control.

Suggested change
'NAME': 'timetable', # Or path to database file if using sqlite3.
'USER': 'timetable', # Not used with sqlite3.
'PASSWORD': 'qOvpzZODQ4n55JKRyRU16PJBEeUde1uK', # Not used with sqlite3.
'HOST': 'simargl.jerse.info', # Set to empty string for localhost. Not used with sqlite3.
'NAME': os.environ.get('DB_NAME', ''), # Or path to database file if using sqlite3.
'USER': os.environ.get('DB_USER', ''), # Not used with sqlite3.
'PASSWORD': os.environ.get('DB_PASSWORD', ''), # Not used with sqlite3.
'HOST': os.environ.get('DB_HOST', ''), # Set to empty string for localhost. Not used with sqlite3.

Copilot uses AI. Check for mistakes.
},
}

import os

#-----------------------------------------
# Django secret key
# Make this unique, and don't share it with anybody.
#-----------------------------------------
SECRET_KEY = 'make_this_unique_and_dont_share_it_with_anybody_sure'

Copilot AI Oct 26, 2025

Copy link

Choose a reason for hiding this comment

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

The SECRET_KEY contains a placeholder value that appears to be committed to version control. Django's SECRET_KEY should be a cryptographically strong random string and should never be hardcoded in settings files. Move this to an environment variable or a secrets management system.

Suggested change
SECRET_KEY = 'make_this_unique_and_dont_share_it_with_anybody_sure'
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
raise RuntimeError("DJANGO_SECRET_KEY environment variable is not set. Please set it to a cryptographically strong random string.")

Copilot uses AI. Check for mistakes.


#-----------------------------------------
# LDAP settings
#-----------------------------------------
# AUTH_LDAP_SERVER_URI = "ldaps://[2001:1470:fffd:ff90::ad1]:3269"
# AUTH_LDAP_SERVER_URI = "ldaps://dcv1fri1.fri1.uni-lj.si:3269"
# AUTH_LDAP_BIND_DN = "CN=ldapp,OU=System;OU=admin;DC=fri1;DC=uni-lj;DC=si"
# AUTH_LDAP_BIND_PASSWORD = "Fu3[EBu`P'JHNuktV]3cNVRN"
# AUTH_LDAP_USER_SEARCH = LDAPSearch(
# "DC=uni-lj,DC=si",
# ldap.SCOPE_SUBTREE, "(userPrincipalName=%(user)s)")
# # Mirror groups in LDAP
# AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
# "DC=uni-lj,DC=si",
# ldap.SCOPE_SUBTREE, "(objectClass=group)"
# )
# AUTH_LDAP_GROUP_TYPE = ULNestedGroupOfNamesType(name_attr="cn")
# AUTH_LDAP_MIRROR_GROUPS = True
# # Only users in this group can log in.
# # AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
# # Populate the Django user from the LDAP directory.
# AUTH_LDAP_USER_ATTR_MAP = {
# "first_name": "givenName",
# "last_name": "sn",
# "email": "mail"
# }
# # AUTH_LDAP_PROFILE_ATTR_MAP = {
# # "employee_number": "employeeNumber"
# # }

# AUTH_LDAP_USER_FLAGS_BY_GROUP = {
# # "is_active": "cn=active,ou=django,ou=groups,dc=example,dc=com",
# # "is_staff": "cn=staff,ou=django,ou=groups,dc=example,dc=com",
# # "is_superuser": "cn=superuser,ou=django,ou=groups,dc=example,dc=com"
# }

# # This is the default, but I like to be explicit.
# AUTH_LDAP_ALWAYS_UPDATE_USER = True
# # Use LDAP group membership to calculate group permissions.
# #AUTH_LDAP_FIND_GROUP_PERMS = True
# # Cache group memberships for an hour to minimize LDAP traffic
# AUTH_LDAP_CACHE_GROUPS = True
# AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600

# -----------------------------------
# Timplate settings

Copilot AI Oct 26, 2025

Copy link

Choose a reason for hiding this comment

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

Corrected spelling of 'Timplate' to 'Template'.

Suggested change
# Timplate settings
# Template settings

Copilot uses AI. Check for mistakes.
# Make sure Debug is False
# -----------------------------------
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
'templates',
],
'APP_DIRS': True,
'OPTIONS': {
'debug': False,
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.request',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
]

# -----------------------------------
# Logging settings
# -----------------------------------
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'console':{
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'file':{
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/home/gregor/urnik/django.log',
'formatter': 'verbose'
},
},
'loggers': {
'django': {
'handlers': ['file'],
'propagate': True,
'level': 'INFO',
},
'friprosveta': {
'handlers': ['console', 'file'],
# 'handlers': ['null'],
'level': 'INFO',
},
'friprosveta': {
'handlers': ['file'],
# 'handlers': ['null'],
Comment on lines +174 to +178

Copilot AI Oct 26, 2025

Copy link

Choose a reason for hiding this comment

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

Duplicate logger key 'friprosveta' defined at lines 171 and 176. The second definition will override the first. These should either be merged into a single logger configuration or one should be renamed if they're meant to be different loggers.

Suggested change
'level': 'INFO',
},
'friprosveta': {
'handlers': ['file'],
# 'handlers': ['null'],

Copilot uses AI. Check for mistakes.
'level': 'DEBUG',
},

}
}

# -----------------------------------
# Studis API settings
# -----------------------------------
STUDIS_API_BASE_URL = 'https://studisfri.uni-lj.si/api'
STUDIS_API_TOKEN = 'sanja-svinja-kokoruz'

Copilot AI Oct 26, 2025

Copy link

Choose a reason for hiding this comment

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

API token is hardcoded in the settings file. This credential should be moved to an environment variable or secrets management system to prevent exposure in version control.

Copilot uses AI. Check for mistakes.


#------------------------------------
# Exchange settings
#------------------------------------
STUDENT_MAPPER_PRODUCTION = True
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@
"class": "logging.StreamHandler",
"formatter": "simple",
},
"file": {
"level": "DEBUG",
"class": "logging.FileHandler",
"filename": "/home/gregor/personal/urnik/debug.log",
"formatter": "verbose",
},
# "file": {
# "level": "DEBUG",
# "class": "logging.FileHandler",
# "filename": "/home/gregor/personal/urnik/debug.log",
# "formatter": "verbose",
# },
},
"loggers": {
"django": {
Expand Down
File renamed without changes.
File renamed without changes.