2 from django.conf import settings
3 from django.core.files import File
4 from django.core.urlresolvers import reverse
5 from django.db import models
6 from jsonfield import JSONField
7 from fnpdjango.storage import BofhFileSystemStorage
9 from curriculum.models import Level, Curriculum, CurriculumCourse
12 bofh_storage = BofhFileSystemStorage()
15 class Section(models.Model):
16 title = models.CharField(max_length=255, unique=True)
17 slug = models.SlugField(max_length=255, unique=True)
18 order = models.IntegerField()
19 xml_file = models.FileField(
20 upload_to="catalogue/section/xml",
21 null=True, blank=True, max_length=255,
23 image = models.ImageField(upload_to="catalogue/section/image", null=True, blank=True)
25 pic = models.ImageField(upload_to="catalogue/section/pic", null=True, blank=True)
26 pic_attribution = models.CharField(max_length=255, null=True, blank=True)
27 pic_src = models.URLField(null=True, blank=True)
29 summary = models.TextField(blank=True, null=True)
34 class IncompleteError(BaseException):
37 def __unicode__(self):
40 def get_absolute_url(self):
41 return "%s#gimnazjum_%s" % (reverse("catalogue_lessons"), self.slug)
44 def publish(cls, infile, ignore_incomplete=False):
45 from librarian.parser import WLDocument
46 from django.core.files.base import ContentFile
47 xml = infile.get_string()
48 wldoc = WLDocument.from_string(xml)
51 for part in wldoc.book_info.parts:
53 lessons.append(Lesson.objects.get(slug=part.slug))
54 except Lesson.DoesNotExist, e:
55 if not ignore_incomplete:
56 raise cls.IncompleteError(part.slug)
58 slug = wldoc.book_info.url.slug
60 section = cls.objects.get(slug=slug)
61 except cls.DoesNotExist:
62 section = cls(slug=slug, order=0)
65 section.xml_file.save('%s.xml' % slug, ContentFile(xml), save=False)
66 section.title = wldoc.book_info.title
69 section.lesson_set.all().update(section=None)
70 for i, lesson in enumerate(lessons):
71 lesson.section = section
77 def syntetic_lesson(self, level):
79 return self.lesson_set.filter(type='synthetic', level=level)[0]
84 class Lesson(models.Model):
85 section = models.ForeignKey(Section, null=True, blank=True)
86 level = models.ForeignKey(Level)
87 title = models.CharField(max_length=255)
88 slug = models.SlugField(max_length=255, unique=True)
89 type = models.CharField(max_length=15, db_index=True)
90 order = models.IntegerField(db_index=True)
91 dc = JSONField(default='{}')
92 curriculum_courses = models.ManyToManyField(CurriculumCourse, blank=True)
93 description = models.TextField(null=True, blank=True)
95 xml_file = models.FileField(
96 upload_to="catalogue/lesson/xml",
97 null=True, blank=True, max_length=255, storage=bofh_storage)
98 html_file = models.FileField(
99 upload_to="catalogue/lesson/html",
100 null=True, blank=True, max_length=255, storage=bofh_storage)
101 package = models.FileField(
102 upload_to="catalogue/lesson/pack",
103 null=True, blank=True, max_length=255, storage=bofh_storage)
104 student_package = models.FileField(
105 upload_to="catalogue/lesson/student_pack",
106 null=True, blank=True, max_length=255, storage=bofh_storage)
107 pdf = models.FileField(
108 upload_to="catalogue/lesson/pdf",
109 null=True, blank=True, max_length=255, storage=bofh_storage)
110 student_pdf = models.FileField(
111 upload_to="catalogue/lesson/student_pdf",
112 null=True, blank=True, max_length=255, storage=bofh_storage)
113 weasy_pdf = models.FileField(
114 upload_to="catalogue/lesson/weasy",
115 null=True, blank=True, max_length=255, storage=bofh_storage)
118 ordering = ['section', 'level', 'order']
120 def __unicode__(self):
124 def get_absolute_url(self):
125 return 'catalogue_lesson', [self.slug]
128 def publish(cls, infile, ignore_incomplete=False, repackage_level=False):
129 from librarian.parser import WLDocument
130 from django.core.files.base import ContentFile
131 wldoc = WLDocument(infile)
133 # Check if not section metadata block.
134 if wldoc.book_info.parts:
135 return Section.publish(infile, ignore_incomplete=ignore_incomplete)
137 slug = wldoc.book_info.url.slug
139 lesson = cls.objects.get(slug=slug)
140 lesson.attachment_set.all().delete()
141 except cls.DoesNotExist:
142 lesson = cls(slug=slug, order=0)
145 lesson.xml_file.save('%s.xml' % slug, ContentFile(infile.get_string()), save=False)
146 lesson.title = wldoc.book_info.title
148 lesson.level = Level.objects.get(meta_name=wldoc.book_info.audience)
150 lesson.populate_description(wldoc=wldoc)
151 lesson.build_html(infile=infile)
153 lesson.build_package()
154 if lesson.type != 'project':
155 lesson.build_pdf(student=True)
156 lesson.build_package(student=True)
158 lesson.level.build_packages()
161 def republish(self, repackage_level=True, attachments=None):
162 from librarian import IOFile
164 from django.conf import settings
165 if attachments is None:
167 for attachment in self.attachment_set.all():
168 full_name = os.path.join(settings.MEDIA_ROOT, attachment.file.name)
169 f = IOFile.from_filename(full_name)
170 attachments['%s.%s' % (attachment.slug, attachment.ext)] = f
171 infile = IOFile.from_filename(self.xml_file.path, attachments=attachments)
172 Lesson.publish(infile, repackage_level=repackage_level)
174 def populate_dc(self):
175 from librarian.parser import WLDocument
176 wldoc = WLDocument.from_file(self.xml_file.path)
177 self.dc = wldoc.book_info.to_dict()
178 self.type = self.dc["type"]
179 assert self.type in ('appendix', 'course', 'synthetic', 'project', 'added', 'added-var'), \
180 u"Unknown lesson type: %s" % self.type
184 for identifier in wldoc.book_info.curriculum:
185 identifier = (identifier or "").replace(' ', '')
189 curr = Curriculum.objects.get(identifier__iexact=identifier)
190 except Curriculum.DoesNotExist:
191 logging.warn('Unknown curriculum course %s in lesson %s' % (identifier, self.slug))
194 courses.add(curr.course)
195 self.curriculum_courses = courses
197 def populate_description(self, wldoc=None, infile=None):
199 wldoc = self.wldocument(infile)
200 if self.type == 'project':
203 lookup = u'Pomysł na lekcję'
204 for header in wldoc.edoc.findall('.//naglowek_rozdzial'):
205 if (header.text or '').strip() == lookup:
206 from lxml import etree
207 self.description = etree.tostring(
208 header.getnext(), method='text', encoding='unicode').strip()
212 def wldocument(self, infile=None):
213 from librarian import IOFile
214 from librarian.parser import WLDocument
215 from .publish import OrmDocProvider
218 infile = IOFile.from_filename(self.xml_file.path)
219 for att in self.attachment_set.all():
220 infile.attachments["%s.%s" % (att.slug, att.ext)] = \
221 IOFile.from_filename(att.file.path)
222 return WLDocument(infile, provider=OrmDocProvider())
224 def build_html(self, infile=None):
225 from .publish import HtmlFormat
226 wldoc = self.wldocument(infile)
227 html = HtmlFormat(wldoc).build()
228 self.html_file.save("%s.html" % self.slug, File(open(html.get_filename())))
230 def build_pdf(self, student=False):
231 from .publish import PdfFormat
232 # PDF uses document with attachments already saved as media,
233 # otherwise sorl.thumbnail complains about SuspiciousOperations.
234 wldoc = self.wldocument()
236 pdf = PdfFormat(wldoc).build()
237 self.student_pdf.save("%s.pdf" % self.slug, File(open(pdf.get_filename())))
239 pdf = PdfFormat(wldoc, teacher=True).build()
240 self.pdf.save("%s.pdf" % self.slug, File(open(pdf.get_filename())))
242 def build_pdf_from_html(self, **kwargs):
243 from .publish import PdfFromHtmlFormat
244 wldoc = self.wldocument()
245 pdf = PdfFromHtmlFormat(
246 wldoc, media_root=settings.MEDIA_ROOT,
247 html_to_pdf_command=settings.HTML_TO_PDF_COMMAND,
249 self.weasy_pdf.save("%s.pdf" % self.slug, File(open(pdf.get_filename())))
251 def add_to_zip(self, zipf, student=False, prefix=''):
252 pdf = self.student_pdf if student else self.pdf
254 zipf.write(pdf.path, "%s%s%s.pdf" % (prefix, self.slug, "_student" if student else ""))
255 for attachment in self.attachment_set.all():
256 zipf.write(attachment.file.path, u"%smaterialy/%s.%s" % (prefix, attachment.slug, attachment.ext))
257 zipf.write(self.xml_file.path, "%spliki-zrodlowe/%s.xml" % (prefix, self.slug))
259 def build_package(self, student=False):
260 from StringIO import StringIO
262 from django.core.files.base import ContentFile
264 zipf = zipfile.ZipFile(buff, 'w', zipfile.ZIP_STORED)
265 self.add_to_zip(zipf, student)
267 fieldname = "student_package" if student else "package"
268 getattr(self, fieldname).save(
269 "%s%s.zip" % (self.slug, "_student" if student else ""),
270 ContentFile(buff.getvalue()))
272 def get_syntetic(self):
273 if self.section is None:
275 return self.section.syntetic_lesson(self.level)
277 def get_other_level(self):
278 if self.section is None:
280 other_levels = self.section.lesson_set.exclude(level=self.level)
281 if other_levels.exists():
282 return other_levels[0].level
284 def get_previous(self):
285 if self.section is None:
288 return self.section.lesson_set.filter(
289 type=self.type, level=self.level,
290 order__lt=self.order).order_by('-order')[0]
295 if self.section is None:
298 return self.section.lesson_set.filter(
299 type=self.type, level=self.level,
300 order__gt=self.order).order_by('order')[0]
304 def requires_internet(self):
305 return any(requirement in self.dc.get('requires', []) for requirement in ('internet', 'Internet'))
308 def attachment_path(instance, filename):
309 return 'catalogue/attachment/%s/%s' % (instance.lesson.slug, filename)
312 class Attachment(models.Model):
313 slug = models.CharField(max_length=255)
314 ext = models.CharField(max_length=15)
315 lesson = models.ForeignKey(Lesson)
316 file = models.FileField(upload_to=attachment_path, storage=bofh_storage, max_length=255)
319 ordering = ['slug', 'ext']
320 unique_together = ['lesson', 'slug', 'ext']
322 def __unicode__(self):
323 return "%s.%s" % (self.slug, self.ext)
326 class Part(models.Model):
327 lesson = models.ForeignKey(Lesson)
328 pdf = models.FileField(upload_to="catalogue/part/pdf", null=True, blank=True)
329 student_pdf = models.FileField(upload_to="catalogue/part/student_pdf", null=True, blank=True)
332 class LessonStub(models.Model):
333 section = models.ForeignKey(Section, null=True, blank=True)
334 level = models.ForeignKey(Level)
335 title = models.CharField(max_length=255)
336 type = models.CharField(max_length=15, db_index=True)
337 order = models.IntegerField(db_index=True)
340 ordering = ['section', 'level', 'order']
342 def __unicode__(self):
349 def add_to_zip(self, *args, **kwargs):