Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
steering
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
AURA
steering
Commits
2b5537b3
Verified
Commit
2b5537b3
authored
1 year ago
by
Ernesto Rico Schmidt
Browse files
Options
Downloads
Patches
Plain Diff
Add type annotations
parent
7b30f5e7
No related branches found
No related tags found
No related merge requests found
Pipeline
#3102
passed
1 year ago
Stage: build
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
program/services.py
+142
-160
142 additions, 160 deletions
program/services.py
with
142 additions
and
160 deletions
program/services.py
+
142
−
160
View file @
2b5537b3
...
...
@@ -41,6 +41,7 @@ class ScheduleData(TypedDict):
add_days_no
:
int
|
None
by_weekday
:
int
|
None
default_playlist_id
:
int
|
None
dryrun
:
bool
|
None
end_time
:
str
first_date
:
str
id
:
int
|
None
...
...
@@ -79,8 +80,14 @@ class Conflicts(TypedDict):
solutions
:
dict
[
str
,
str
]
# TODO: add type annotations
def
resolve_conflicts
(
data
,
schedule_pk
,
show_pk
):
class
ScheduleCreateUpdateData
(
TypedDict
):
notes
:
dict
playlists
:
dict
schedule
:
ScheduleData
solutions
:
dict
[
str
,
str
]
def
resolve_conflicts
(
data
:
ScheduleCreateUpdateData
,
schedule_pk
:
int
|
None
,
show_pk
:
int
):
"""
Resolves conflicts
Expects JSON POST/PUT data from /shows/1/schedules/
...
...
@@ -89,21 +96,20 @@ def resolve_conflicts(data, schedule_pk, show_pk):
Returns an empty list if resolution was successful
"""
s
dl
=
data
[
"
schedule
"
]
solutions
=
data
.
get
(
"
solutions
"
,
[])
s
chedule
=
data
[
"
schedule
"
]
solutions
=
data
.
get
(
"
solutions
"
,
[])
# only needed if conflicts exist
# Regenerate conflicts
schedule
=
instantiate_upcoming_schedule
(
sdl
,
show_pk
,
schedule_pk
)
show
=
schedule
.
show
conflicts
=
make_conflicts
(
sdl
,
schedule_pk
,
show_pk
)
new_schedule
=
instantiate_upcoming_schedule
(
schedule
,
show_pk
,
schedule_pk
)
show
=
new_schedule
.
show
conflicts
=
make_conflicts
(
schedule
,
schedule_pk
,
show_pk
)
if
schedule
.
rrule
.
freq
>
0
and
schedule
.
first_date
==
schedule
.
last_date
:
if
new_
schedule
.
rrule
.
freq
>
0
and
new_
schedule
.
first_date
==
new_
schedule
.
last_date
:
raise
ValidationError
(
_
(
"
Start and end dates must not be the same.
"
),
code
=
"
no-same-day-start-and-end
"
,
)
if
schedule
.
last_date
<
schedule
.
first_date
:
if
new_
schedule
.
last_date
<
new_
schedule
.
first_date
:
raise
ValidationError
(
_
(
"
End date mustn
'
t be before start.
"
),
code
=
"
no-start-after-end
"
,
...
...
@@ -118,177 +124,159 @@ def resolve_conflicts(data, schedule_pk, show_pk):
conflicts
=
conflicts
,
)
# Projected timeslots to create
create
=
[]
to_create
:
list
[
TimeSlot
]
=
[]
to_update
:
list
[
TimeSlot
]
=
[]
to_delete
:
list
[
TimeSlot
]
=
[]
# Existing timeslots to update
update
=
[]
# Existing timeslots to delete
delete
=
[]
# Error messages
errors
=
{}
for
t
s
in
conflicts
[
"
projected
"
]:
# If no solution necessary
#
#
- C
reate
the projected timeslot and skip
#
if
"
solution_choices
"
not
in
ts
or
len
(
ts
[
"
collisions
"
])
<
1
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
for
t
imeslot
in
conflicts
[
"
projected
"
]:
# If no solution necessary
: Create the projected timeslot and skip
if
"
solution_choices
"
not
in
timeslot
or
len
(
timeslot
[
"
collisions
"
])
==
0
:
to_c
reate
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
continue
# Check hash (if start, end, rrule or by_weekday changed)
if
not
t
s
[
"
hash
"
]
in
solutions
:
errors
[
t
s
[
"
hash
"
]]
=
_
(
"
This change on the timeslot is not allowed.
"
)
if
not
t
imeslot
[
"
hash
"
]
in
solutions
:
errors
[
t
imeslot
[
"
hash
"
]]
=
_
(
"
This change on the timeslot is not allowed.
"
)
continue
# If no resolution given
#
# - Skip
#
if
solutions
[
ts
[
"
hash
"
]]
==
""
:
errors
[
ts
[
"
hash
"
]]
=
_
(
"
No solution given.
"
)
# If no resolution given: skip
if
solutions
[
timeslot
[
"
hash
"
]]
==
""
:
errors
[
timeslot
[
"
hash
"
]]
=
_
(
"
No solution given.
"
)
continue
# If resolution is not accepted for this conflict
#
# - Skip
#
if
not
solutions
[
ts
[
"
hash
"
]]
in
ts
[
"
solution_choices
"
]:
errors
[
ts
[
"
hash
"
]]
=
_
(
"
Given solution is not accepted for this conflict.
"
)
# If resolution is not accepted for this conflict: SKIP
if
not
solutions
[
timeslot
[
"
hash
"
]]
in
timeslot
[
"
solution_choices
"
]:
errors
[
timeslot
[
"
hash
"
]]
=
_
(
"
Given solution is not accepted for this conflict.
"
)
continue
"""
Conflict resolution
"""
existing
=
t
s
[
"
collisions
"
][
0
]
solution
=
solutions
[
t
s
[
"
hash
"
]]
existing
=
t
imeslot
[
"
collisions
"
][
0
]
solution
=
solutions
[
t
imeslot
[
"
hash
"
]]
# theirs
#
# - Discard the projected timeslot
# - Keep the existing collision(s)
#
if
solution
==
"
theirs
"
:
# - Discard the projected timeslot
# - Keep the existing collision(s)
continue
# ours
#
# - Create the projected timeslot
# - Delete the existing collision(s)
#
if
solution
==
"
ours
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
# - Create the projected timeslot
# - Delete the existing collision(s)
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
# Delete collision(s)
for
ex
in
ts
[
"
collisions
"
]:
for
collision
in
timeslot
[
"
collisions
"
]:
try
:
existing_ts
=
TimeSlot
.
objects
.
get
(
pk
=
ex
[
"
id
"
])
delete
.
append
(
existing_ts
)
to_delete
.
append
(
TimeSlot
.
objects
.
get
(
pk
=
collision
[
"
id
"
]))
except
ObjectDoesNotExist
:
pass
# theirs-end
#
# - Keep the existing timeslot
# - Create projected with end of existing start
#
if
solution
==
"
theirs-end
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
existing
[
"
start
"
],
schedule
,
show
# - Keep the existing timeslot
# - Create projected with end of existing start
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
existing
[
"
start
"
],
new_schedule
,
show
),
)
create
.
append
(
projected_ts
)
# ours-end
#
# - Create the projected timeslot
# - Change the start of the existing collision to projected end
#
if
solution
==
"
ours-end
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
# - Create the projected timeslot
# - Change the start of the existing collision to projected end
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
existing_ts
=
TimeSlot
.
objects
.
get
(
pk
=
existing
[
"
id
"
])
existing_ts
.
start
=
parse_datetime
(
ts
[
"
end
"
])
update
.
append
(
existing_ts
)
# theirs-start
#
# - Keep existing
# - Create projected with start time of existing end
#
existing_ts
.
start
=
parse_datetime
(
timeslot
[
"
end
"
])
to_update
.
append
(
existing_ts
)
if
solution
==
"
theirs-start
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
existing
[
"
end
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
# ours-start
#
# - Create the projected timeslot
# - Change end of existing to projected start
#
# - Keep existing
# - Create projected with start time of existing end
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
existing
[
"
end
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
if
solution
==
"
ours-start
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
# - Create the projected timeslot
# - Change end of existing to projected start
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
existing_ts
=
TimeSlot
.
objects
.
get
(
pk
=
existing
[
"
id
"
])
existing_ts
.
end
=
parse_datetime
(
ts
[
"
start
"
])
update
.
append
(
existing_ts
)
# theirs-both
#
# - Keep existing
# - Create two projected timeslots with end of existing start and start of existing
# end
#
existing_ts
.
end
=
parse_datetime
(
timeslot
[
"
start
"
])
to_update
.
append
(
existing_ts
)
if
solution
==
"
theirs-both
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
existing
[
"
start
"
],
schedule
,
show
# - Keep existing
# - Create two projected timeslots with end of existing start and start of existing end
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
existing
[
"
start
"
],
new_schedule
,
show
),
)
create
.
append
(
projected_ts
)
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
existing
[
"
end
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
# ours-both
#
# - Create projected
# - Split existing into two:
# - Set existing end time to projected start
# - Create another one with start = projected end and end = existing end
#
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
existing
[
"
end
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
if
solution
==
"
ours-both
"
:
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
start
"
],
ts
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
# - Create projected
# - Split existing into two
# - Set existing end time to projected start
# - Create another one with start = projected end and end = existing end
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
start
"
],
timeslot
[
"
end
"
],
new_schedule
,
show
),
)
existing_ts
=
TimeSlot
.
objects
.
get
(
pk
=
existing
[
"
id
"
])
existing_ts
.
end
=
parse_datetime
(
t
s
[
"
start
"
])
update
.
append
(
existing_ts
)
existing_ts
.
end
=
parse_datetime
(
t
imeslot
[
"
start
"
])
to_
update
.
append
(
existing_ts
)
projected_ts
=
TimeSlot
.
objects
.
instantiate
(
ts
[
"
end
"
],
existing
[
"
end
"
],
schedule
,
show
)
create
.
append
(
projected_ts
)
to_create
.
append
(
TimeSlot
.
objects
.
instantiate
(
timeslot
[
"
end
"
],
existing
[
"
end
"
],
new_schedule
,
show
),
)
# If there were any errors, don't make any db changes yet
# but add error messages and return already chosen solutions
if
len
(
errors
)
>
0
:
conflicts
=
make_conflicts
(
model_to_dict
(
schedule
),
schedule
.
pk
,
show
.
pk
)
conflicts
=
make_conflicts
(
model_to_dict
(
new_
schedule
),
new_
schedule
.
pk
,
show
.
pk
)
partly_resolved
=
conflicts
[
"
projected
"
]
saved_solutions
=
{}
# Add already chosen resolutions and error message to conflict
for
index
,
c
in
enumerate
(
conflicts
[
"
projected
"
]):
for
index
,
projected_entry
in
enumerate
(
conflicts
[
"
projected
"
]):
# The element should only exist if there was a collision
if
len
(
c
[
"
collisions
"
])
>
0
:
saved_solutions
[
c
[
"
hash
"
]]
=
""
if
len
(
projected_entry
[
"
collisions
"
])
>
0
:
saved_solutions
[
projected_entry
[
"
hash
"
]]
=
""
if
c
[
"
hash
"
]
in
solutions
and
solutions
[
c
[
"
hash
"
]]
in
c
[
"
solution_choices
"
]:
saved_solutions
[
c
[
"
hash
"
]]
=
solutions
[
c
[
"
hash
"
]]
if
(
projected_entry
[
"
hash
"
]
in
solutions
and
solutions
[
projected_entry
[
"
hash
"
]]
in
projected_entry
[
"
solution_choices
"
]
):
saved_solutions
[
projected_entry
[
"
hash
"
]]
=
solutions
[
projected_entry
[
"
hash
"
]]
if
c
[
"
hash
"
]
in
errors
:
partly_resolved
[
index
][
"
error
"
]
=
errors
[
c
[
"
hash
"
]]
if
projected_entry
[
"
hash
"
]
in
errors
:
partly_resolved
[
index
][
"
error
"
]
=
errors
[
projected_entry
[
"
hash
"
]]
# Re-insert post data
conflicts
[
"
projected
"
]
=
partly_resolved
...
...
@@ -302,61 +290,55 @@ def resolve_conflicts(data, schedule_pk, show_pk):
conflicts
=
conflicts
,
)
# Collect upcoming timeslots to delete which might still remain
del_timeslots
=
TimeSlot
.
objects
.
filter
(
schedule
=
schedule
,
start__gt
=
timezone
.
make_aware
(
datetime
.
combine
(
schedule
.
last_date
,
time
(
0
,
0
))),
remaining_timeslots
=
TimeSlot
.
objects
.
filter
(
schedule
=
new_schedule
,
start__gt
=
timezone
.
make_aware
(
datetime
.
combine
(
new_schedule
.
last_date
,
time
(
0
,
0
))),
)
for
del_ts
in
del
_timeslots
:
delete
.
append
(
del_ts
)
for
timeslot
in
remaining
_timeslots
:
to_
delete
.
append
(
timeslot
)
# If 'dryrun' is true, just return the projected changes instead of executing them
if
"
dryrun
"
in
s
dl
and
sdl
[
"
dryrun
"
]:
if
"
dryrun
"
in
s
chedule
and
schedule
[
"
dryrun
"
]:
return
{
"
create
"
:
[
model_to_dict
(
t
s
)
for
ts
in
create
],
"
update
"
:
[
model_to_dict
(
t
s
)
for
ts
in
update
],
"
delete
"
:
[
model_to_dict
(
t
s
)
for
ts
in
delete
],
"
create
"
:
[
model_to_dict
(
t
imeslot
)
for
timeslot
in
to_
create
],
"
update
"
:
[
model_to_dict
(
t
imeslot
)
for
timeslot
in
to_
update
],
"
delete
"
:
[
model_to_dict
(
t
imeslot
)
for
timeslot
in
to_
delete
],
}
"""
Database changes if no errors found
"""
#
Database changes if no errors found
# Only save schedule if timeslots were created
if
create
:
# Create or update schedule
schedule
.
save
()
if
to_create
:
new_schedule
.
save
()
# Update timeslots
for
ts
in
update
:
ts
.
save
(
update_fields
=
[
"
start
"
,
"
end
"
])
for
timeslot
in
to_update
:
timeslot
.
save
(
update_fields
=
[
"
start
"
,
"
end
"
])
# Create timeslots
for
ts
in
create
:
ts
.
schedule
=
schedule
for
timeslot
in
to_create
:
timeslot
.
schedule
=
new_schedule
# Reassign playlists
if
"
playlists
"
in
data
and
t
s
.
hash
in
data
[
"
playlists
"
]:
t
s
.
playlist_id
=
int
(
data
[
"
playlists
"
][
t
s
.
hash
])
if
"
playlists
"
in
data
and
t
imeslot
.
hash
in
data
[
"
playlists
"
]:
t
imeslot
.
playlist_id
=
int
(
data
[
"
playlists
"
][
t
imeslot
.
hash
])
t
s
.
save
()
t
imeslot
.
save
()
# Reassign notes
if
"
notes
"
in
data
and
t
s
.
hash
in
data
[
"
notes
"
]:
if
"
notes
"
in
data
and
t
imeslot
.
hash
in
data
[
"
notes
"
]:
try
:
note
=
Note
.
objects
.
get
(
pk
=
int
(
data
[
"
notes
"
][
t
s
.
hash
]))
note
.
timeslot_id
=
t
s
.
id
note
=
Note
.
objects
.
get
(
pk
=
int
(
data
[
"
notes
"
][
t
imeslot
.
hash
]))
note
.
timeslot_id
=
t
imeslot
.
id
note
.
save
(
update_fields
=
[
"
timeslot_id
"
])
timeslot
=
TimeSlot
.
objects
.
get
(
pk
=
t
s
.
id
)
timeslot
=
TimeSlot
.
objects
.
get
(
pk
=
t
imeslot
.
id
)
timeslot
.
note_id
=
note
.
id
timeslot
.
save
(
update_fields
=
[
"
note_id
"
])
except
ObjectDoesNotExist
:
pass
# Delete manually resolved timeslots and those after until
for
dl
in
delete
:
dl
.
delete
()
for
timeslot
in
to_delete
:
timeslot
.
delete
()
return
model_to_dict
(
schedule
)
return
model_to_dict
(
new_
schedule
)
def
instantiate_upcoming_schedule
(
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment