We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Pentabarf export calculates duration incorrectly for items spanning multiple slots
The pentabarf,xml export does the wrong thing if an item spans multiple slots. It calculates the duration from just the first slot, rather than the entire item.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wafer/schedule/admin.py`
Content:
```
1 import datetime
2 
3 from django.conf.urls import url
4 from django.contrib import admin
5 from django.contrib import messages
6 from django.utils.encoding import force_text
7 from django.utils.translation import ugettext as _
8 from django import forms
9 
10 from wafer.schedule.models import Day, Venue, Slot, ScheduleItem
11 from wafer.talks.models import Talk, ACCEPTED
12 from wafer.pages.models import Page
13 from wafer.utils import cache_result
14 
15 
16 # These are functions to simplify testing
17 def find_overlapping_slots():
18     """Find any slots that overlap"""
19     overlaps = set([])
20     all_slots = list(Slot.objects.all())
21     for slot in all_slots:
22         # Because slots are ordered, we can be more efficient than this
23         # N^2 loop, but this is simple and, since the number of slots
24         # should be low, this should be "fast enough"
25         start = slot.get_start_time()
26         end = slot.end_time
27         for other_slot in all_slots:
28             if other_slot.pk == slot.pk:
29                 continue
30             if other_slot.get_day() != slot.get_day():
31                 # different days, can't overlap
32                 continue
33             # Overlap if the start_time or end_time is bounded by our times
34             # start_time <= other.start_time < end_time
35             # or
36             # start_time < other.end_time <= end_time
37             other_start = other_slot.get_start_time()
38             other_end = other_slot.end_time
39             if start <= other_start and other_start < end:
40                 overlaps.add(slot)
41                 overlaps.add(other_slot)
42             elif start < other_end and other_end <= end:
43                 overlaps.add(slot)
44                 overlaps.add(other_slot)
45     return overlaps
46 
47 
48 def validate_items(all_items=None):
49     """Find errors in the schedule. Check for:
50          - pending / rejected talks in the schedule
51          - items with both talks and pages assigned
52          - items with neither talks nor pages assigned
53          """
54     if all_items is None:
55         all_items = prefetch_schedule_items()
56     validation = []
57     for item in all_items:
58         if item.talk is not None and item.page is not None:
59             validation.append(item)
60         elif item.talk is None and item.page is None:
61             validation.append(item)
62         elif item.talk and item.talk.status != ACCEPTED:
63             validation.append(item)
64     return validation
65 
66 
67 def find_duplicate_schedule_items(all_items=None):
68     """Find talks / pages assigned to mulitple schedule items"""
69     if all_items is None:
70         all_items = prefetch_schedule_items()
71     duplicates = []
72     seen_talks = {}
73     for item in all_items:
74         if item.talk and item.talk in seen_talks:
75             duplicates.append(item)
76             if seen_talks[item.talk] not in duplicates:
77                 duplicates.append(seen_talks[item.talk])
78         else:
79             seen_talks[item.talk] = item
80         # We currently allow duplicate pages for cases were we need disjoint
81         # schedule items, like multiple open space sessions on different
82         # days and similar cases. This may be revisited later
83     return duplicates
84 
85 
86 def find_clashes(all_items=None):
87     """Find schedule items which clash (common slot and venue)"""
88     if all_items is None:
89         all_items = prefetch_schedule_items()
90     clashes = {}
91     seen_venue_slots = {}
92     for item in all_items:
93         for slot in item.slots.all():
94             pos = (item.venue, slot)
95             if pos in seen_venue_slots:
96                 if seen_venue_slots[pos] not in clashes:
97                     clashes[pos] = [seen_venue_slots[pos]]
98                 clashes[pos].append(item)
99             else:
100                 seen_venue_slots[pos] = item
101     return clashes
102 
103 
104 def find_invalid_venues(all_items=None):
105     """Find venues assigned slots that aren't on the allowed list
106        of days."""
107     if all_items is None:
108         all_items = prefetch_schedule_items()
109     venues = {}
110     for item in all_items:
111         valid = False
112         item_days = list(item.venue.days.all())
113         for slot in item.slots.all():
114             for day in item_days:
115                 if day == slot.get_day():
116                     valid = True
117                     break
118         if not valid:
119             venues.setdefault(item.venue, [])
120             venues[item.venue].append(item)
121     return venues
122 
123 
124 def prefetch_schedule_items():
125     """Prefetch all schedule items and related objects."""
126     return list(ScheduleItem.objects
127                 .select_related(
128                     'talk', 'page', 'venue')
129                 .prefetch_related(
130                     'slots', 'slots__previous_slot', 'slots__day')
131                 .all())
132 
133 
134 @cache_result('wafer_schedule_check_schedule', 60*60)
135 def check_schedule():
136     """Helper routine to eaily test if the schedule is valid"""
137     all_items = prefetch_schedule_items()
138     if find_clashes(all_items):
139         return False
140     if find_duplicate_schedule_items(all_items):
141         return False
142     if validate_items(all_items):
143         return False
144     if find_overlapping_slots():
145         return False
146     if find_invalid_venues(all_items):
147         return False
148     return True
149 
150 
151 class ScheduleItemAdminForm(forms.ModelForm):
152     class Meta:
153         model = ScheduleItem
154         fields = ('slots', 'venue', 'talk', 'page', 'details', 'notes',
155                   'css_class')
156 
157     def __init__(self, *args, **kwargs):
158         super(ScheduleItemAdminForm, self).__init__(*args, **kwargs)
159         self.fields['talk'].queryset = Talk.objects.filter(status=ACCEPTED)
160         # Present all pages as possible entries in the schedule
161         self.fields['page'].queryset = Page.objects.all()
162 
163 
164 class ScheduleItemAdmin(admin.ModelAdmin):
165     form = ScheduleItemAdminForm
166 
167     change_list_template = 'admin/scheduleitem_list.html'
168     list_display = ['get_start_time', 'venue', 'get_title']
169 
170     # We stuff these validation results into the view, rather than
171     # enforcing conditions on the actual model, since it can be hard
172     # to edit the schedule and keep it entirely consistent at every
173     # step (think exchanging talks and so forth)
174     def changelist_view(self, request, extra_context=None):
175         extra_context = extra_context or {}
176         # Find issues in the schedule
177         clashes = find_clashes()
178         validation = validate_items()
179         venues = find_invalid_venues()
180         duplicates = find_duplicate_schedule_items()
181         errors = {}
182         if clashes:
183             errors['clashes'] = clashes
184         if duplicates:
185             errors['duplicates'] = duplicates
186         if validation:
187             errors['validation'] = validation
188         if venues:
189             errors['venues'] = venues
190         extra_context['errors'] = errors
191         return super(ScheduleItemAdmin, self).changelist_view(request,
192                                                               extra_context)
193 
194     def get_urls(self):
195         from wafer.schedule.views import ScheduleEditView
196 
197         urls = super(ScheduleItemAdmin, self).get_urls()
198         admin_schedule_edit_view = self.admin_site.admin_view(
199             ScheduleEditView.as_view())
200         my_urls = [
201             url(r'^edit/$', admin_schedule_edit_view, name='schedule_editor'),
202             url(r'^edit/(?P<day_id>[0-9]+)$', admin_schedule_edit_view,
203                 name='schedule_editor'),
204         ]
205         return my_urls + urls
206 
207 
208 class SlotAdminForm(forms.ModelForm):
209 
210     class Meta:
211         model = Slot
212         fields = ('name', 'previous_slot', 'day', 'start_time', 'end_time')
213 
214     class Media:
215         js = ('js/scheduledatetime.js',)
216 
217 
218 class SlotAdminAddForm(SlotAdminForm):
219 
220     # Additional field added for creating multiple slots at once
221     additional = forms.IntegerField(min_value=0, max_value=30, required=False,
222                                     label=_("Additional slots"),
223                                     help_text=_("Create this number of "
224                                                 "additional slots following"
225                                                 "this one"))
226 
227 
228 class SlotAdmin(admin.ModelAdmin):
229     form = SlotAdminForm
230 
231     list_display = ('__str__', 'day', 'end_time')
232     list_editable = ('end_time',)
233 
234     change_list_template = 'admin/slot_list.html'
235 
236     def changelist_view(self, request, extra_context=None):
237         extra_context = extra_context or {}
238         # Find issues with the slots
239         errors = {}
240         overlaps = find_overlapping_slots()
241         if overlaps:
242             errors['overlaps'] = overlaps
243         extra_context['errors'] = errors
244         return super(SlotAdmin, self).changelist_view(request,
245                                                       extra_context)
246 
247     def get_form(self, request, obj=None, **kwargs):
248         """Change the form depending on whether we're adding or
249            editing the slot."""
250         if obj is None:
251             # Adding a new Slot
252             kwargs['form'] = SlotAdminAddForm
253         return super(SlotAdmin, self).get_form(request, obj, **kwargs)
254 
255     def save_model(self, request, obj, form, change):
256         super(SlotAdmin, self).save_model(request, obj, form, change)
257         if not change and form.cleaned_data['additional'] > 0:
258             # We add the requested additional slots
259             # All created slot will have the same length as the slot just
260             # created , and we specify them as a sequence using
261             # "previous_slot" so tweaking start times is simple.
262             prev = obj
263             end = datetime.datetime.combine(prev.day.date, prev.end_time)
264             start = datetime.datetime.combine(prev.day.date,
265                                               prev.get_start_time())
266             slot_len = end - start
267             for loop in range(form.cleaned_data['additional']):
268                 end = end + slot_len
269                 new_slot = Slot(day=prev.day, previous_slot=prev,
270                                 end_time=end.time())
271                 new_slot.save()
272                 msgdict = {'obj': force_text(new_slot)}
273                 msg = _("Additional slot %(obj)s added sucessfully") % msgdict
274                 if hasattr(request, '_messages'):
275                     # Don't add messages unless we have a suitable request
276                     # Needed during testing, and possibly in other cases
277                     self.message_user(request, msg, messages.SUCCESS)
278                 prev = new_slot
279 
280 
281 admin.site.register(Day)
282 admin.site.register(Slot, SlotAdmin)
283 admin.site.register(Venue)
284 admin.site.register(ScheduleItem, ScheduleItemAdmin)
285
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/wafer/schedule/admin.py b/wafer/schedule/admin.py
--- a/wafer/schedule/admin.py
+++ b/wafer/schedule/admin.py
@@ -45,6 +45,25 @@
     return overlaps
 
 
+def find_non_contiguous(all_items=None):
+    """Find any items that have slots that aren't contiguous"""
+    if all_items is None:
+        all_items = prefetch_schedule_items()
+    non_contiguous = []
+    for item in all_items:
+        if item.slots.count() < 2:
+            # No point in checking
+            continue
+        last_slot = None
+        for slot in item.slots.all().order_by('end_time'):
+            if last_slot:
+                if last_slot.end_time != slot.get_start_time():
+                    non_contiguous.append(item)
+                    break
+            last_slot = slot
+    return non_contiguous
+
+
 def validate_items(all_items=None):
     """Find errors in the schedule. Check for:
          - pending / rejected talks in the schedule
@@ -143,6 +162,8 @@
         return False
     if find_overlapping_slots():
         return False
+    if find_non_contiguous(all_items):
+        return False
     if find_invalid_venues(all_items):
         return False
     return True
@@ -174,10 +195,12 @@
     def changelist_view(self, request, extra_context=None):
         extra_context = extra_context or {}
         # Find issues in the schedule
+        all_items = None
         clashes = find_clashes()
-        validation = validate_items()
+        validation = validate_items(all_items)
         venues = find_invalid_venues()
-        duplicates = find_duplicate_schedule_items()
+        duplicates = find_duplicate_schedule_items(all_items)
+        non_contiguous = find_non_contiguous(all_items)
         errors = {}
         if clashes:
             errors['clashes'] = clashes
@@ -187,6 +210,8 @@
             errors['validation'] = validation
         if venues:
             errors['venues'] = venues
+        if non_contiguous:
+            errors['non_contiguous'] = non_contiguous
         extra_context['errors'] = errors
         return super(ScheduleItemAdmin, self).changelist_view(request,
                                                               extra_context)

{"golden_diff": "diff --git a/wafer/schedule/admin.py b/wafer/schedule/admin.py\n--- a/wafer/schedule/admin.py\n+++ b/wafer/schedule/admin.py\n@@ -45,6 +45,25 @@\n     return overlaps\n \n \n+def find_non_contiguous(all_items=None):\n+    \"\"\"Find any items that have slots that aren't contiguous\"\"\"\n+    if all_items is None:\n+        all_items = prefetch_schedule_items()\n+    non_contiguous = []\n+    for item in all_items:\n+        if item.slots.count() < 2:\n+            # No point in checking\n+            continue\n+        last_slot = None\n+        for slot in item.slots.all().order_by('end_time'):\n+            if last_slot:\n+                if last_slot.end_time != slot.get_start_time():\n+                    non_contiguous.append(item)\n+                    break\n+            last_slot = slot\n+    return non_contiguous\n+\n+\n def validate_items(all_items=None):\n     \"\"\"Find errors in the schedule. Check for:\n          - pending / rejected talks in the schedule\n@@ -143,6 +162,8 @@\n         return False\n     if find_overlapping_slots():\n         return False\n+    if find_non_contiguous(all_items):\n+        return False\n     if find_invalid_venues(all_items):\n         return False\n     return True\n@@ -174,10 +195,12 @@\n     def changelist_view(self, request, extra_context=None):\n         extra_context = extra_context or {}\n         # Find issues in the schedule\n+        all_items = None\n         clashes = find_clashes()\n-        validation = validate_items()\n+        validation = validate_items(all_items)\n         venues = find_invalid_venues()\n-        duplicates = find_duplicate_schedule_items()\n+        duplicates = find_duplicate_schedule_items(all_items)\n+        non_contiguous = find_non_contiguous(all_items)\n         errors = {}\n         if clashes:\n             errors['clashes'] = clashes\n@@ -187,6 +210,8 @@\n             errors['validation'] = validation\n         if venues:\n             errors['venues'] = venues\n+        if non_contiguous:\n+            errors['non_contiguous'] = non_contiguous\n         extra_context['errors'] = errors\n         return super(ScheduleItemAdmin, self).changelist_view(request,\n                                                               extra_context)\n", "issue": "Pentabarf export calculates duration incorrectly for items spanning multiple slots\nThe pentabarf,xml export does the wrong thing if an item spans multiple slots. It calculates the duration from just the first slot, rather than the entire item.\n\n", "before_files": [{"content": "import datetime\n\nfrom django.conf.urls import url\nfrom django.contrib import admin\nfrom django.contrib import messages\nfrom django.utils.encoding import force_text\nfrom django.utils.translation import ugettext as _\nfrom django import forms\n\nfrom wafer.schedule.models import Day, Venue, Slot, ScheduleItem\nfrom wafer.talks.models import Talk, ACCEPTED\nfrom wafer.pages.models import Page\nfrom wafer.utils import cache_result\n\n\n# These are functions to simplify testing\ndef find_overlapping_slots():\n    \"\"\"Find any slots that overlap\"\"\"\n    overlaps = set([])\n    all_slots = list(Slot.objects.all())\n    for slot in all_slots:\n        # Because slots are ordered, we can be more efficient than this\n        # N^2 loop, but this is simple and, since the number of slots\n        # should be low, this should be \"fast enough\"\n        start = slot.get_start_time()\n        end = slot.end_time\n        for other_slot in all_slots:\n            if other_slot.pk == slot.pk:\n                continue\n            if other_slot.get_day() != slot.get_day():\n                # different days, can't overlap\n                continue\n            # Overlap if the start_time or end_time is bounded by our times\n            # start_time <= other.start_time < end_time\n            # or\n            # start_time < other.end_time <= end_time\n            other_start = other_slot.get_start_time()\n            other_end = other_slot.end_time\n            if start <= other_start and other_start < end:\n                overlaps.add(slot)\n                overlaps.add(other_slot)\n            elif start < other_end and other_end <= end:\n                overlaps.add(slot)\n                overlaps.add(other_slot)\n    return overlaps\n\n\ndef validate_items(all_items=None):\n    \"\"\"Find errors in the schedule. Check for:\n         - pending / rejected talks in the schedule\n         - items with both talks and pages assigned\n         - items with neither talks nor pages assigned\n         \"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    validation = []\n    for item in all_items:\n        if item.talk is not None and item.page is not None:\n            validation.append(item)\n        elif item.talk is None and item.page is None:\n            validation.append(item)\n        elif item.talk and item.talk.status != ACCEPTED:\n            validation.append(item)\n    return validation\n\n\ndef find_duplicate_schedule_items(all_items=None):\n    \"\"\"Find talks / pages assigned to mulitple schedule items\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    duplicates = []\n    seen_talks = {}\n    for item in all_items:\n        if item.talk and item.talk in seen_talks:\n            duplicates.append(item)\n            if seen_talks[item.talk] not in duplicates:\n                duplicates.append(seen_talks[item.talk])\n        else:\n            seen_talks[item.talk] = item\n        # We currently allow duplicate pages for cases were we need disjoint\n        # schedule items, like multiple open space sessions on different\n        # days and similar cases. This may be revisited later\n    return duplicates\n\n\ndef find_clashes(all_items=None):\n    \"\"\"Find schedule items which clash (common slot and venue)\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    clashes = {}\n    seen_venue_slots = {}\n    for item in all_items:\n        for slot in item.slots.all():\n            pos = (item.venue, slot)\n            if pos in seen_venue_slots:\n                if seen_venue_slots[pos] not in clashes:\n                    clashes[pos] = [seen_venue_slots[pos]]\n                clashes[pos].append(item)\n            else:\n                seen_venue_slots[pos] = item\n    return clashes\n\n\ndef find_invalid_venues(all_items=None):\n    \"\"\"Find venues assigned slots that aren't on the allowed list\n       of days.\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    venues = {}\n    for item in all_items:\n        valid = False\n        item_days = list(item.venue.days.all())\n        for slot in item.slots.all():\n            for day in item_days:\n                if day == slot.get_day():\n                    valid = True\n                    break\n        if not valid:\n            venues.setdefault(item.venue, [])\n            venues[item.venue].append(item)\n    return venues\n\n\ndef prefetch_schedule_items():\n    \"\"\"Prefetch all schedule items and related objects.\"\"\"\n    return list(ScheduleItem.objects\n                .select_related(\n                    'talk', 'page', 'venue')\n                .prefetch_related(\n                    'slots', 'slots__previous_slot', 'slots__day')\n                .all())\n\n\n@cache_result('wafer_schedule_check_schedule', 60*60)\ndef check_schedule():\n    \"\"\"Helper routine to eaily test if the schedule is valid\"\"\"\n    all_items = prefetch_schedule_items()\n    if find_clashes(all_items):\n        return False\n    if find_duplicate_schedule_items(all_items):\n        return False\n    if validate_items(all_items):\n        return False\n    if find_overlapping_slots():\n        return False\n    if find_invalid_venues(all_items):\n        return False\n    return True\n\n\nclass ScheduleItemAdminForm(forms.ModelForm):\n    class Meta:\n        model = ScheduleItem\n        fields = ('slots', 'venue', 'talk', 'page', 'details', 'notes',\n                  'css_class')\n\n    def __init__(self, *args, **kwargs):\n        super(ScheduleItemAdminForm, self).__init__(*args, **kwargs)\n        self.fields['talk'].queryset = Talk.objects.filter(status=ACCEPTED)\n        # Present all pages as possible entries in the schedule\n        self.fields['page'].queryset = Page.objects.all()\n\n\nclass ScheduleItemAdmin(admin.ModelAdmin):\n    form = ScheduleItemAdminForm\n\n    change_list_template = 'admin/scheduleitem_list.html'\n    list_display = ['get_start_time', 'venue', 'get_title']\n\n    # We stuff these validation results into the view, rather than\n    # enforcing conditions on the actual model, since it can be hard\n    # to edit the schedule and keep it entirely consistent at every\n    # step (think exchanging talks and so forth)\n    def changelist_view(self, request, extra_context=None):\n        extra_context = extra_context or {}\n        # Find issues in the schedule\n        clashes = find_clashes()\n        validation = validate_items()\n        venues = find_invalid_venues()\n        duplicates = find_duplicate_schedule_items()\n        errors = {}\n        if clashes:\n            errors['clashes'] = clashes\n        if duplicates:\n            errors['duplicates'] = duplicates\n        if validation:\n            errors['validation'] = validation\n        if venues:\n            errors['venues'] = venues\n        extra_context['errors'] = errors\n        return super(ScheduleItemAdmin, self).changelist_view(request,\n                                                              extra_context)\n\n    def get_urls(self):\n        from wafer.schedule.views import ScheduleEditView\n\n        urls = super(ScheduleItemAdmin, self).get_urls()\n        admin_schedule_edit_view = self.admin_site.admin_view(\n            ScheduleEditView.as_view())\n        my_urls = [\n            url(r'^edit/$', admin_schedule_edit_view, name='schedule_editor'),\n            url(r'^edit/(?P<day_id>[0-9]+)$', admin_schedule_edit_view,\n                name='schedule_editor'),\n        ]\n        return my_urls + urls\n\n\nclass SlotAdminForm(forms.ModelForm):\n\n    class Meta:\n        model = Slot\n        fields = ('name', 'previous_slot', 'day', 'start_time', 'end_time')\n\n    class Media:\n        js = ('js/scheduledatetime.js',)\n\n\nclass SlotAdminAddForm(SlotAdminForm):\n\n    # Additional field added for creating multiple slots at once\n    additional = forms.IntegerField(min_value=0, max_value=30, required=False,\n                                    label=_(\"Additional slots\"),\n                                    help_text=_(\"Create this number of \"\n                                                \"additional slots following\"\n                                                \"this one\"))\n\n\nclass SlotAdmin(admin.ModelAdmin):\n    form = SlotAdminForm\n\n    list_display = ('__str__', 'day', 'end_time')\n    list_editable = ('end_time',)\n\n    change_list_template = 'admin/slot_list.html'\n\n    def changelist_view(self, request, extra_context=None):\n        extra_context = extra_context or {}\n        # Find issues with the slots\n        errors = {}\n        overlaps = find_overlapping_slots()\n        if overlaps:\n            errors['overlaps'] = overlaps\n        extra_context['errors'] = errors\n        return super(SlotAdmin, self).changelist_view(request,\n                                                      extra_context)\n\n    def get_form(self, request, obj=None, **kwargs):\n        \"\"\"Change the form depending on whether we're adding or\n           editing the slot.\"\"\"\n        if obj is None:\n            # Adding a new Slot\n            kwargs['form'] = SlotAdminAddForm\n        return super(SlotAdmin, self).get_form(request, obj, **kwargs)\n\n    def save_model(self, request, obj, form, change):\n        super(SlotAdmin, self).save_model(request, obj, form, change)\n        if not change and form.cleaned_data['additional'] > 0:\n            # We add the requested additional slots\n            # All created slot will have the same length as the slot just\n            # created , and we specify them as a sequence using\n            # \"previous_slot\" so tweaking start times is simple.\n            prev = obj\n            end = datetime.datetime.combine(prev.day.date, prev.end_time)\n            start = datetime.datetime.combine(prev.day.date,\n                                              prev.get_start_time())\n            slot_len = end - start\n            for loop in range(form.cleaned_data['additional']):\n                end = end + slot_len\n                new_slot = Slot(day=prev.day, previous_slot=prev,\n                                end_time=end.time())\n                new_slot.save()\n                msgdict = {'obj': force_text(new_slot)}\n                msg = _(\"Additional slot %(obj)s added sucessfully\") % msgdict\n                if hasattr(request, '_messages'):\n                    # Don't add messages unless we have a suitable request\n                    # Needed during testing, and possibly in other cases\n                    self.message_user(request, msg, messages.SUCCESS)\n                prev = new_slot\n\n\nadmin.site.register(Day)\nadmin.site.register(Slot, SlotAdmin)\nadmin.site.register(Venue)\nadmin.site.register(ScheduleItem, ScheduleItemAdmin)\n", "path": "wafer/schedule/admin.py"}], "after_files": [{"content": "import datetime\n\nfrom django.conf.urls import url\nfrom django.contrib import admin\nfrom django.contrib import messages\nfrom django.utils.encoding import force_text\nfrom django.utils.translation import ugettext as _\nfrom django import forms\n\nfrom wafer.schedule.models import Day, Venue, Slot, ScheduleItem\nfrom wafer.talks.models import Talk, ACCEPTED\nfrom wafer.pages.models import Page\nfrom wafer.utils import cache_result\n\n\n# These are functions to simplify testing\ndef find_overlapping_slots():\n    \"\"\"Find any slots that overlap\"\"\"\n    overlaps = set([])\n    all_slots = list(Slot.objects.all())\n    for slot in all_slots:\n        # Because slots are ordered, we can be more efficient than this\n        # N^2 loop, but this is simple and, since the number of slots\n        # should be low, this should be \"fast enough\"\n        start = slot.get_start_time()\n        end = slot.end_time\n        for other_slot in all_slots:\n            if other_slot.pk == slot.pk:\n                continue\n            if other_slot.get_day() != slot.get_day():\n                # different days, can't overlap\n                continue\n            # Overlap if the start_time or end_time is bounded by our times\n            # start_time <= other.start_time < end_time\n            # or\n            # start_time < other.end_time <= end_time\n            other_start = other_slot.get_start_time()\n            other_end = other_slot.end_time\n            if start <= other_start and other_start < end:\n                overlaps.add(slot)\n                overlaps.add(other_slot)\n            elif start < other_end and other_end <= end:\n                overlaps.add(slot)\n                overlaps.add(other_slot)\n    return overlaps\n\n\ndef find_non_contiguous(all_items=None):\n    \"\"\"Find any items that have slots that aren't contiguous\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    non_contiguous = []\n    for item in all_items:\n        if item.slots.count() < 2:\n            # No point in checking\n            continue\n        last_slot = None\n        for slot in item.slots.all().order_by('end_time'):\n            if last_slot:\n                if last_slot.end_time != slot.get_start_time():\n                    non_contiguous.append(item)\n                    break\n            last_slot = slot\n    return non_contiguous\n\n\ndef validate_items(all_items=None):\n    \"\"\"Find errors in the schedule. Check for:\n         - pending / rejected talks in the schedule\n         - items with both talks and pages assigned\n         - items with neither talks nor pages assigned\n         \"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    validation = []\n    for item in all_items:\n        if item.talk is not None and item.page is not None:\n            validation.append(item)\n        elif item.talk is None and item.page is None:\n            validation.append(item)\n        elif item.talk and item.talk.status != ACCEPTED:\n            validation.append(item)\n    return validation\n\n\ndef find_duplicate_schedule_items(all_items=None):\n    \"\"\"Find talks / pages assigned to mulitple schedule items\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    duplicates = []\n    seen_talks = {}\n    for item in all_items:\n        if item.talk and item.talk in seen_talks:\n            duplicates.append(item)\n            if seen_talks[item.talk] not in duplicates:\n                duplicates.append(seen_talks[item.talk])\n        else:\n            seen_talks[item.talk] = item\n        # We currently allow duplicate pages for cases were we need disjoint\n        # schedule items, like multiple open space sessions on different\n        # days and similar cases. This may be revisited later\n    return duplicates\n\n\ndef find_clashes(all_items=None):\n    \"\"\"Find schedule items which clash (common slot and venue)\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    clashes = {}\n    seen_venue_slots = {}\n    for item in all_items:\n        for slot in item.slots.all():\n            pos = (item.venue, slot)\n            if pos in seen_venue_slots:\n                if seen_venue_slots[pos] not in clashes:\n                    clashes[pos] = [seen_venue_slots[pos]]\n                clashes[pos].append(item)\n            else:\n                seen_venue_slots[pos] = item\n    return clashes\n\n\ndef find_invalid_venues(all_items=None):\n    \"\"\"Find venues assigned slots that aren't on the allowed list\n       of days.\"\"\"\n    if all_items is None:\n        all_items = prefetch_schedule_items()\n    venues = {}\n    for item in all_items:\n        valid = False\n        item_days = list(item.venue.days.all())\n        for slot in item.slots.all():\n            for day in item_days:\n                if day == slot.get_day():\n                    valid = True\n                    break\n        if not valid:\n            venues.setdefault(item.venue, [])\n            venues[item.venue].append(item)\n    return venues\n\n\ndef prefetch_schedule_items():\n    \"\"\"Prefetch all schedule items and related objects.\"\"\"\n    return list(ScheduleItem.objects\n                .select_related(\n                    'talk', 'page', 'venue')\n                .prefetch_related(\n                    'slots', 'slots__previous_slot', 'slots__day')\n                .all())\n\n\n@cache_result('wafer_schedule_check_schedule', 60*60)\ndef check_schedule():\n    \"\"\"Helper routine to eaily test if the schedule is valid\"\"\"\n    all_items = prefetch_schedule_items()\n    if find_clashes(all_items):\n        return False\n    if find_duplicate_schedule_items(all_items):\n        return False\n    if validate_items(all_items):\n        return False\n    if find_overlapping_slots():\n        return False\n    if find_non_contiguous(all_items):\n        return False\n    if find_invalid_venues(all_items):\n        return False\n    return True\n\n\nclass ScheduleItemAdminForm(forms.ModelForm):\n    class Meta:\n        model = ScheduleItem\n        fields = ('slots', 'venue', 'talk', 'page', 'details', 'notes',\n                  'css_class')\n\n    def __init__(self, *args, **kwargs):\n        super(ScheduleItemAdminForm, self).__init__(*args, **kwargs)\n        self.fields['talk'].queryset = Talk.objects.filter(status=ACCEPTED)\n        # Present all pages as possible entries in the schedule\n        self.fields['page'].queryset = Page.objects.all()\n\n\nclass ScheduleItemAdmin(admin.ModelAdmin):\n    form = ScheduleItemAdminForm\n\n    change_list_template = 'admin/scheduleitem_list.html'\n    list_display = ['get_start_time', 'venue', 'get_title']\n\n    # We stuff these validation results into the view, rather than\n    # enforcing conditions on the actual model, since it can be hard\n    # to edit the schedule and keep it entirely consistent at every\n    # step (think exchanging talks and so forth)\n    def changelist_view(self, request, extra_context=None):\n        extra_context = extra_context or {}\n        # Find issues in the schedule\n        all_items = None\n        clashes = find_clashes()\n        validation = validate_items(all_items)\n        venues = find_invalid_venues()\n        duplicates = find_duplicate_schedule_items(all_items)\n        non_contiguous = find_non_contiguous(all_items)\n        errors = {}\n        if clashes:\n            errors['clashes'] = clashes\n        if duplicates:\n            errors['duplicates'] = duplicates\n        if validation:\n            errors['validation'] = validation\n        if venues:\n            errors['venues'] = venues\n        if non_contiguous:\n            errors['non_contiguous'] = non_contiguous\n        extra_context['errors'] = errors\n        return super(ScheduleItemAdmin, self).changelist_view(request,\n                                                              extra_context)\n\n    def get_urls(self):\n        from wafer.schedule.views import ScheduleEditView\n\n        urls = super(ScheduleItemAdmin, self).get_urls()\n        admin_schedule_edit_view = self.admin_site.admin_view(\n            ScheduleEditView.as_view())\n        my_urls = [\n            url(r'^edit/$', admin_schedule_edit_view, name='schedule_editor'),\n            url(r'^edit/(?P<day_id>[0-9]+)$', admin_schedule_edit_view,\n                name='schedule_editor'),\n        ]\n        return my_urls + urls\n\n\nclass SlotAdminForm(forms.ModelForm):\n\n    class Meta:\n        model = Slot\n        fields = ('name', 'previous_slot', 'day', 'start_time', 'end_time')\n\n    class Media:\n        js = ('js/scheduledatetime.js',)\n\n\nclass SlotAdminAddForm(SlotAdminForm):\n\n    # Additional field added for creating multiple slots at once\n    additional = forms.IntegerField(min_value=0, max_value=30, required=False,\n                                    label=_(\"Additional slots\"),\n                                    help_text=_(\"Create this number of \"\n                                                \"additional slots following\"\n                                                \"this one\"))\n\n\nclass SlotAdmin(admin.ModelAdmin):\n    form = SlotAdminForm\n\n    list_display = ('__str__', 'day', 'end_time')\n    list_editable = ('end_time',)\n\n    change_list_template = 'admin/slot_list.html'\n\n    def changelist_view(self, request, extra_context=None):\n        extra_context = extra_context or {}\n        # Find issues with the slots\n        errors = {}\n        overlaps = find_overlapping_slots()\n        if overlaps:\n            errors['overlaps'] = overlaps\n        extra_context['errors'] = errors\n        return super(SlotAdmin, self).changelist_view(request,\n                                                      extra_context)\n\n    def get_form(self, request, obj=None, **kwargs):\n        \"\"\"Change the form depending on whether we're adding or\n           editing the slot.\"\"\"\n        if obj is None:\n            # Adding a new Slot\n            kwargs['form'] = SlotAdminAddForm\n        return super(SlotAdmin, self).get_form(request, obj, **kwargs)\n\n    def save_model(self, request, obj, form, change):\n        super(SlotAdmin, self).save_model(request, obj, form, change)\n        if not change and form.cleaned_data['additional'] > 0:\n            # We add the requested additional slots\n            # All created slot will have the same length as the slot just\n            # created , and we specify them as a sequence using\n            # \"previous_slot\" so tweaking start times is simple.\n            prev = obj\n            end = datetime.datetime.combine(prev.day.date, prev.end_time)\n            start = datetime.datetime.combine(prev.day.date,\n                                              prev.get_start_time())\n            slot_len = end - start\n            for loop in range(form.cleaned_data['additional']):\n                end = end + slot_len\n                new_slot = Slot(day=prev.day, previous_slot=prev,\n                                end_time=end.time())\n                new_slot.save()\n                msgdict = {'obj': force_text(new_slot)}\n                msg = _(\"Additional slot %(obj)s added sucessfully\") % msgdict\n                if hasattr(request, '_messages'):\n                    # Don't add messages unless we have a suitable request\n                    # Needed during testing, and possibly in other cases\n                    self.message_user(request, msg, messages.SUCCESS)\n                prev = new_slot\n\n\nadmin.site.register(Day)\nadmin.site.register(Slot, SlotAdmin)\nadmin.site.register(Venue)\nadmin.site.register(ScheduleItem, ScheduleItemAdmin)\n", "path": "wafer/schedule/admin.py"}]}