Commit cc77088c authored by PabloLeCrapaud's avatar PabloLeCrapaud
Browse files

Merge branch 'Cosmo-no-slot' into Iti-Bis

parents bdfe493b 96d6f3f4
......@@ -4,9 +4,10 @@
*.bak
FlOpEDT/logs/*
FlOpEDT/*.log
FlOpEDT/*.lp
*.sol
*.mps
.idea/
.vscode/
node_modules
*.lp
......@@ -7,9 +7,8 @@ ARG CONFIG
RUN mkdir /code
WORKDIR /code
RUN apt-get update && apt-get -y install memcached
COPY requirements.txt /code/
RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
COPY docker/requirements /code/requirements/
......
"""
ASGI entrypoint. Configures Django and then runs the application
defined in the ASGI_APPLICATION setting.
"""
import os
import django
from channels.routing import get_default_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "FlOpEDT.settings.dockerless-production")
django.setup()
application = get_default_application()
\ No newline at end of file
......@@ -30,7 +30,7 @@ DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'flop_database_ibis',
'NAME': 'flop_database_american_cosmo',
'USER': 'flop_user',
'PASSWORD': 'your_password',
'HOST': '127.0.0.1',
......
......@@ -26,22 +26,61 @@ from .base import *
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ['POSTGRES_DB'],
'USER': os.environ['POSTGRES_USER'],
'PASSWORD': os.environ['POSTGRES_PASSWORD'],
'HOST': 'db',
'PORT': 5432,
'NAME': os.environ.get('POSTGRES_DB', 'postgres'),
'USER': os.environ.get('POSTGRES_USER', 'postgres'),
'HOST': os.environ.get('POSTGRES_HOST', 'localhost'),
'PORT': os.environ.get('POSTGRES_PORT', '5432'),
'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
}
}
REDIS_HOST = os.environ.get('REDIS_HOST', 'localhost')
REDIS_BACKEND = f'redis://{REDIS_HOST}:6379/1'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis", 6379)],
"hosts": [(REDIS_HOST, 6379)],
},
},
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': f'{os.environ.get("CACHE_HOST", "localhost")}:11211',
}
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '[{asctime}] - {levelname} - {module} - {message}',
'style': '{',
},
"ROUTING": "solve_board.routing.channel_routing",
},
'handlers': {
'console': {
'level': os.environ.get('DJANGO_LOG_LEVEL', 'WARNING'),
'class': 'logging.StreamHandler',
'formatter': 'simple',
}
},
'loggers': {
'': {
'handlers': ['console'],
'level': os.environ.get('DJANGO_LOG_LEVEL', 'WARNING'),
'propagate': True,
},
'django.db.backends': {
'level': os.environ.get('DB_LOG_LEVEL', 'WARNING'),
'handlers': ['console'],
'propagate': True,
}
}
}
# SECURITY WARNING: don't run with debug turned on in production!
......
......@@ -29,6 +29,7 @@ from TTapp.TTModel import TTModel, max_weight
from TTapp.models import MinHalfDays
from misc.deploy_database.nb_salaries import nb_salaries
from misc.assign_colors import assign_tutor_color
from MyFlOp.MyTTUtils import print_differences
class MyTTModel(TTModel):
......@@ -113,7 +114,6 @@ class MyTTModel(TTModel):
def solve(self, time_limit=3600, target_work_copy=None,
solver='gurobi'):
"""
......
......@@ -27,7 +27,8 @@
import functools
from TTapp.TTUtils import basic_reassign_rooms, basic_swap_version
from base.models import Department
from base.models import ScheduledCourse, Department
from people.models import Tutor
def resolve_department(func):
......@@ -44,6 +45,22 @@ def resolve_department(func):
return _wraper_function
def print_differences(week, year, old_copy, new_copy, tutors=Tutor.objects.all()):
for tutor in tutors:
SCa = ScheduledCourse.objects.filter(cours__tutor=tutor, copie_travail=old_copy, cours__semaine=week,
cours__an=year)
SCb = ScheduledCourse.objects.filter(cours__tutor=tutor, copie_travail=new_copy, cours__semaine=week,
cours__an=year)
slots_a = set([x.creneau for x in SCa])
slots_b = set([x.creneau for x in SCb])
if slots_a ^ slots_b:
result = "For %s old copy has :" % tutor
for sl in slots_a - slots_b:
result += "%s, " % sl
result += "and new copy has :"
for sl in slots_b - slots_a:
result += "%s, " % sl
print(result)
@resolve_department
def reassign_rooms(department, week, year, target_work_copy):
......
This diff is collapsed.
......@@ -43,27 +43,31 @@ from FlOpEDT.filters import DropdownFilterAll, DropdownFilterRel, \
DropdownFilterCho
class LimitNaturePerPeriodAdmin(DepartmentModelAdmin):
list_display = ('week', 'year', 'train_prog', 'type', 'limit',
'module', 'tutor', 'period', 'comment')
class LimitCourseTypePerPeriodAdmin(DepartmentModelAdmin):
list_display = ('week',
'year',
'train_prog',
'type',
'limit',
'period',
'comment')
ordering = ()
list_filter = (('train_prog', DropdownFilterRel),
('week', DropdownFilterAll),
('year', DropdownFilterAll),
('tutor', DropdownFilterRel),
('tutors', DropdownFilterRel),
('type', DropdownFilterRel),
)
class ReasonableDaysAdmin(DepartmentModelAdmin):
list_display = ('week', 'year', 'train_prog',
'group', 'tutor', 'comment')
list_display = ('week', 'year', 'train_prog', 'comment')
ordering = ()
list_filter = (('train_prog', DropdownFilterRel),
('week', DropdownFilterAll),
('year', DropdownFilterAll),
('group', DropdownFilterRel),
('tutor', DropdownFilterRel),
('groups', DropdownFilterRel),
('tutors', DropdownFilterRel),
)
......@@ -125,7 +129,7 @@ class SimultaneousCoursesAdmin(DepartmentModelAdmin):
)
admin.site.register(LimitCourseTypePerPeriod, LimitNaturePerPeriodAdmin)
admin.site.register(LimitCourseTypePerPeriod, LimitCourseTypePerPeriodAdmin)
admin.site.register(ReasonableDays, ReasonableDaysAdmin)
admin.site.register(Stabilize, StabilizeAdmin)
admin.site.register(MinHalfDays, MinHalfDaysAdmin)
......
# Generated by Django 2.1.2 on 2018-12-19 08:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('base', '0015_auto_20181219_0858'),
('people', '0004_auto_20181219_0858'),
('TTapp', '0007_auto_20181113_1031'),
]
operations = [
migrations.RemoveField(
model_name='limitcoursetypeperperiod',
name='tutor',
),
migrations.RemoveField(
model_name='reasonabledays',
name='group',
),
migrations.RemoveField(
model_name='reasonabledays',
name='tutor',
),
migrations.AddField(
model_name='limitcoursetypeperperiod',
name='tutors',
field=models.ManyToManyField(blank=True, related_name='Course_type_limits', to='people.Tutor'),
),
migrations.AddField(
model_name='reasonabledays',
name='groups',
field=models.ManyToManyField(blank=True, related_name='reasonable_day_constraints', to='base.Group'),
),
migrations.AddField(
model_name='reasonabledays',
name='tutors',
field=models.ManyToManyField(blank=True, related_name='reasonable_day_constraints', to='people.Tutor'),
),
migrations.AddField(
model_name='stabilize',
name='fixed_days',
field=models.ManyToManyField(blank=True, related_name='days_to_fix', to='base.Day'),
),
migrations.AlterField(
model_name='limitcoursetypeperperiod',
name='module',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Module'),
),
migrations.AlterField(
model_name='minhalfdays',
name='group',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Group'),
),
migrations.AlterField(
model_name='minhalfdays',
name='module',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='base.Module'),
),
]
This diff is collapsed.
import base.models as base
from django.test import TestCase
from unittest.mock import patch, Mock, call
from base.models import Department, TrainingProgramme, Course, CourseType, Time
from people.models import Tutor
from TTapp.models import LimitCourseTypePerPeriod
from TTapp.TTModel import TTModel
class TTConstraintTestCase(TestCase):
fixtures = ['dump.json']
def setUp(self):
base.Department.objects.all().delete()
self.department1 = base.Department.objects.create(name="departement1", abbrev="dept1")
self.department2 = base.Department.objects.create(name="departement2", abbrev="dept2")
self.tp1 = base.TrainingProgramme.objects.create(name="TrainingProgramme1", abbrev="tp1", department=self.department1)
self.tp2 = base.TrainingProgramme.objects.create(name="TrainingProgramme2", abbrev="tp2", department=self.department2)
self.ct1 = base.CourseType.objects.create(name="CourseType1")
self.info = Department.objects.get(abbrev="info")
self.dut1 = TrainingProgramme.objects.get(abbrev='INFO1')
self.dut2 = TrainingProgramme.objects.get(abbrev='INFO2')
self.c_basic = LimitCourseTypePerPeriod.objects.create(limit=0, type=self.ct1, department=self.department1)
self.DS = CourseType.objects.get(name='CTRL')
self.CM = CourseType.objects.get(name='CM')
self.TD = CourseType.objects.get(name='TD')
self.TP = CourseType.objects.get(name='TP')
self.c_2018 = LimitCourseTypePerPeriod.objects.create(train_prog=self.tp1, year=2018, limit=0, type=self.ct1, department=self.department1)
self.c_2018_39 = LimitCourseTypePerPeriod.objects.create(train_prog=self.tp1, year=2018, week=39, limit=0, type=self.ct1, department=self.department1)
self.c_2018_39_without_tp = LimitCourseTypePerPeriod.objects.create(year=2018, week=39, limit=0, type=self.ct1, department=self.department1)
self.c_basic = LimitCourseTypePerPeriod.objects.create(limit=1, type=self.TD, department=self.info)
self.c_tp2 = LimitCourseTypePerPeriod.objects.create(train_prog=self.tp2, limit=0, type=self.ct1, department=self.department1)
self.c_2019_1 = LimitCourseTypePerPeriod.objects.create(train_prog=self.tp2, year=2019, week=1, limit=0, type=self.ct1, department=self.department1)
self.c_2018 = LimitCourseTypePerPeriod.objects.create(train_prog=self.dut1, year=2018, limit=1, type=self.TD, department=self.info)
self.c_2018_39 = LimitCourseTypePerPeriod.objects.create(train_prog=self.dut1, year=2018, week=39, limit=0, type=self.TD, department=self.info)
self.c_2018_39_without_tp = LimitCourseTypePerPeriod.objects.create(year=2018, week=39, limit=0, type=self.TD, department=self.info)
self.c_tp2 = LimitCourseTypePerPeriod.objects.create(train_prog=self.dut2, limit=0, type=self.TD, department=self.info)
self.c_2019_1 = LimitCourseTypePerPeriod.objects.create(train_prog=self.dut2, year=2019, week=1, limit=0, type=self.TD, department=self.info)
def test_constraint_without_training_program(self):
view_model = self.c_2018_39_without_tp.get_viewmodel()
self.assertEqual(view_model['train_prog'], 'All')
\ No newline at end of file
self.assertEqual(view_model['details']['train_prog'], 'All')
@patch('TTapp.models.LimitCourseTypePerPeriod.register_expression')
def test_limit_register_expression_without_tutors(self, register_expression):
attrs = {'wdb.days': (0,1,2,)}
ttmodel = Mock(**attrs)
period_by_day = [
(0, Time.AM),
(0, Time.PM),
(1, Time.AM),
(1, Time.PM),
(2, Time.AM),
(2, Time.PM),
]
calls = [call(ttmodel, period_by_day, 1.),]
constraint = LimitCourseTypePerPeriod.objects.create(limit=1, type=self.TD, department=self.info)
constraint.period == LimitCourseTypePerPeriod.FULL_DAY
constraint.enrich_model(ttmodel)
register_expression.assert_has_calls(calls)
@patch('TTapp.models.LimitCourseTypePerPeriod.register_expression')
def test_limit_register_expression_with_tutors(self, register_expression):
constraint = LimitCourseTypePerPeriod.objects.create(limit=1, type=self.TD, department=self.info)
constraint.period == LimitCourseTypePerPeriod.FULL_DAY
calls = []
attrs = {'wdb.days': (0,1,2,)}
ttmodel = Mock(**attrs)
period_by_day = [
(0, Time.AM),
(0, Time.PM),
(1, Time.AM),
(1, Time.PM),
(2, Time.AM),
(2, Time.PM),
]
for tutor in Tutor.objects.filter(username__in=['AB', 'AJ', 'CDU', 'FMA']):
calls.append(call(ttmodel, period_by_day, 1., tutor=tutor))
constraint.tutors.add(tutor)
constraint.enrich_model(ttmodel)
register_expression.assert_has_calls(calls)
@patch('TTapp.models.ReasonableDays.register_expression')
def test_reasonable_register_expression_with_tutors(self, register_expression):
pass
\ No newline at end of file
......@@ -10,7 +10,7 @@ class TTutilsTestCase(TestCase):
cls.department2 = models.Department.objects.create(name="departement2", abbrev="dept2")
cls.tp1 = models.TrainingProgramme.objects.create(name="TrainingProgramme1", abbrev="tp1", department=cls.department1)
cls.tp2 = models.TrainingProgramme.objects.create(name="TrainingProgramme2", abbrev="tp2", department=cls.department2)
cls.gt1 = models.GroupType.objects.create(name="groupe_type_1")
cls.gt1 = models.GroupType.objects.create(name="groupe_type_1", department=cls.department1)
cls.g1 = models.Group.objects.create(nom="gp1", train_prog=cls.tp1, type=cls.gt1, size=0)
cls.ct1 = models.CourseType.objects.create(name="CourseType1")
cls.p1 = models.Period.objects.create(name="annee_complete", starting_week=0, ending_week=53)
......
......@@ -22,28 +22,26 @@
# a commercial license. Buying such a license is mandatory as soon as
# you develop activities involving the FlOpEDT/FlOpScheduler software
# without disclosing the source code of your own applications.
from import_export import resources, fields
from import_export.widgets import ForeignKeyWidget
import logging
from django.contrib import admin
from django.core.exceptions import FieldDoesNotExist
from django.db .models.fields import related as related_fields
import django.contrib.auth as auth
from base.models import Day, RoomGroup, Module, Course, Group, Slot, \
UserPreference, Time, ScheduledCourse, EdtVersion, CourseModification, \
PlanningModification, BreakingNews, TrainingProgramme, ModuleDisplay, \
TutorDisplay, Regen, Holiday, TrainingHalfDay, RoomPreference, RoomSort, \
CoursePreference, Dependency, RoomType, Department
CoursePreference, Dependency, RoomType, Department, CourseType
from base.models import CourseType
from core.department import get_department_lookup
from people.models import Tutor, User
import django.contrib.auth as auth
from django.core.exceptions import FieldDoesNotExist
from django.db .models.fields import related as related_fields
from import_export import resources, fields
from import_export.widgets import ForeignKeyWidget
from FlOpEDT.filters import DropdownFilterAll, DropdownFilterRel, \
DropdownFilterCho
......@@ -89,12 +87,12 @@ class CoursPlaceResource(resources.ModelResource):
module = fields.Field(column_name='module',
attribute='cours__module',
widget=ForeignKeyWidget(Module, 'abbrev'))
jour = fields.Field(column_name='jour',
attribute='creneau__jour',
widget=ForeignKeyWidget(Day, 'no'))
heure = fields.Field(column_name='heure',
attribute='creneau__heure',
widget=ForeignKeyWidget(Time, 'no'))
coursetype = fields.Field(column_name='coursetype',
attribute='cours__type',
widget=ForeignKeyWidget(CourseType, 'name'))
# day = fields.Field(column_name='day',
# attribute='day',
# widget=ForeignKeyWidget(Day, 'no'))
# salle = fields.Field(column_name = 'salle',
# attribute = 'salle',
# widget = ForeignKeyWidget(Salle,'nom'))
......@@ -114,8 +112,8 @@ class CoursPlaceResource(resources.ModelResource):
class Meta:
model = ScheduledCourse
fields = ('id', 'no', 'groupe', 'promo', 'color_bg', 'color_txt',
'module', 'jour', 'heure', 'semaine', 'room', 'prof',
'room_type')
'module', 'coursetype', 'day', 'start_time',
'semaine', 'room', 'prof', 'room_type')
class CoursResource(resources.ModelResource):
......@@ -128,6 +126,11 @@ class CoursResource(resources.ModelResource):
module = fields.Field(column_name='module',
attribute='module',
widget=ForeignKeyWidget(Module, 'abbrev'))
coursetype = fields.Field(column_name='coursetype',
attribute='type',
widget=ForeignKeyWidget(CourseType, 'name'))
duration = fields.Field(column_name='duration',
attribute='cours__type__duration')
groupe = fields.Field(column_name='groupe',
attribute='groupe',
widget=ForeignKeyWidget(Group, 'nom'))
......@@ -144,7 +147,7 @@ class CoursResource(resources.ModelResource):
class Meta:
model = Course
fields = ('id', 'no', 'tutor_name', 'groupe', 'promo', 'module',
'color_bg', 'color_txt', 'prof', 'room_type')
'coursetype', 'color_bg', 'color_txt', 'prof', 'room_type')
class SemaineAnResource(resources.ModelResource):
......@@ -156,27 +159,16 @@ class SemaineAnResource(resources.ModelResource):
class DispoResource(resources.ModelResource):
prof = fields.Field(attribute='user',
widget=ForeignKeyWidget(User, 'username'))
jour = fields.Field(attribute='creneau__jour',
widget=ForeignKeyWidget(Day, 'no'))
heure = fields.Field(attribute='creneau__heure',
widget=ForeignKeyWidget(Time, 'no'))
class Meta:
model = UserPreference
fields = ('jour', 'heure', 'valeur', 'prof')
fields = ('day', 'start_time', 'duration', 'valeur', 'prof')
class UnavailableRoomsResource(resources.ModelResource):
day = fields.Field(column_name='jour',
attribute='creneau__jour',
widget=ForeignKeyWidget(Day, 'no'))
slot = fields.Field(column_name='heure',
attribute='creneau__heure',
widget=ForeignKeyWidget(Time, 'no'))
class Meta:
model = RoomPreference
fields = ("room", "day", "slot")
fields = ("room", "day", "start_time", "duration")
class BreakingNewsResource(resources.ModelResource):
......@@ -313,8 +305,9 @@ class RoomGroupAdmin(DepartmentModelAdmin):
class RoomPreferenceAdmin(DepartmentModelAdmin):
list_display = ('room', 'semaine', 'an', 'creneau', 'valeur')
ordering = ('-an','-semaine','creneau')
list_display = ('room', 'semaine', 'an', 'day', 'start_time',
'duration', 'valeur')
ordering = ('-an','-semaine', 'day', 'start_time')
list_filter = (
('room', DropdownFilterRel),
('an', DropdownFilterAll),
......@@ -366,8 +359,8 @@ class CoursPlaceAdmin(DepartmentModelAdmin):
cours_an.short_description = 'Année'
cours_an.admin_order_field = 'cours__an'
list_display = (cours_semaine, cours_an, 'cours', 'creneau', 'room')
ordering = ('creneau', 'cours', 'room')
list_display = (cours_semaine, cours_an, 'cours', 'day', 'start_time', 'room')
ordering = ('day', 'start_time', 'cours', 'room')
list_filter = (
('cours__tutor', DropdownFilterRel),
('cours__an', DropdownFilterAll),
......@@ -383,8 +376,8 @@ class EdtVersionAdmin(DepartmentModelAdmin):
class CoursePreferenceAdmin(DepartmentModelAdmin):
list_display = ('course_type', 'train_prog', 'creneau',
'valeur', 'semaine', 'an')
list_display = ('course_type', 'train_prog', 'day', 'start_time',
'duration', 'valeur', 'semaine', 'an')
ordering = ('-an', '-semaine')
list_filter = (('semaine', DropdownFilterAll),
('an', DropdownFilterAll),
......@@ -425,8 +418,8 @@ class CourseModificationAdmin(DepartmentModelAdmin):
cours_an.admin_order_field = 'cours__an'
list_display = ('cours', cours_semaine, cours_an,
'version_old', 'room_old', 'creneau_old',
'updated_at', 'initiator'
'version_old', 'room_old', 'day_old',
'start_time_old', 'updated_at', 'initiator'
)
list_filter = (('initiator', DropdownFilterRel),
('cours__an', DropdownFilterAll),
......@@ -447,9 +440,10 @@ class PlanningModificationAdmin(DepartmentModelAdmin):
class DispoAdmin(DepartmentModelAdmin):
list_display = ('user', 'creneau', 'valeur', 'semaine', 'an')
ordering = ('user', 'an', 'semaine', 'creneau', 'valeur')
list_filter = (('creneau', DropdownFilterRel),
list_display = ('user', 'day', 'start_time', 'duration', 'valeur',
'semaine', 'an')
ordering = ('user', 'an', 'semaine', 'day', 'start_time', 'valeur')
list_filter = (('start_time', DropdownFilterAll),
('semaine', DropdownFilterAll),
('user', DropdownFilterRel),
)
......
......@@ -9,6 +9,12 @@ class PeriodWeeks():
"""
def __init__(self, department=None, year=None, start=None, end=None, exclude_empty_weeks=False):
"""
Parameters
----------
exclude_empty_weeks : bool
exclude weeks from lists when no courses are planned (default is False)
"""
self .department = department
......@@ -142,6 +148,9 @@ class PeriodWeeks():