class BaseFileModel(models.Model): name = models.CharField(_('Name'), max_length=255) description = models.CharField(max_length=255, blank=True) folder = select2_fields.ForeignKey(UserFolder) uploaded_at = models.DateTimeField(auto_now_add=True) created_by = select2_fields.ForeignKey( User, related_name='%(app_label)s_%(class)s_created', on_delete=models.CASCADE) def save(self, **kwargs): path, ext = os.path.splitext(self.file.name) # if not self.name or self.name == "": self.name = os.path.basename(path) return super(BaseFileModel, self).save(**kwargs) def class_name(self): return self.__class__.__name__ def file_exist(self): return (self.file and os.path.isfile(self.file.path)) class Meta: abstract = True ordering = ['name']
class Broadcaster(models.Model): name = models.CharField(_('name'), max_length=200, unique=True) slug = models.SlugField( _('Slug'), unique=True, max_length=200, help_text=_( u'Used to access this instance, the "slug" is a short label ' + 'containing only letters, numbers, underscore or dash top.'), editable=False, default="") # default empty, fill it in save building = models.ForeignKey('Building', verbose_name=_('Building')) description = RichTextField(_('description'), config_name='complete', blank=True) poster = models.ForeignKey(CustomImageModel, models.SET_NULL, blank=True, null=True, verbose_name=_('Poster')) url = models.URLField(_('URL'), help_text=_('Url of the stream'), unique=True) video_on_hold = select2_fields.ForeignKey( Video, help_text=_( 'This video will be displayed when there is no live stream.'), blank=True, null=True, verbose_name=_('Video on hold')) status = models.BooleanField( default=0, help_text=_('Check if the broadcaster is currently sending stream.')) is_restricted = models.BooleanField( verbose_name=_(u'Restricted access'), help_text=_('Live is accessible only to authenticated users.'), default=False) def __str__(self): return "%s - %s" % (self.name, self.url) def get_poster_url(self): if self.poster: return self.poster.file.url else: thumbnail_url = ''.join([settings.STATIC_URL, DEFAULT_THUMBNAIL]) return thumbnail_url def save(self, *args, **kwargs): self.slug = slugify(self.name) super(Broadcaster, self).save(*args, **kwargs) class Meta: verbose_name = _("Broadcaster") verbose_name_plural = _("Broadcasters") ordering = ['building', 'name'] @property def sites(self): return self.building.sites
class Recording(models.Model): recorder = models.ForeignKey( Recorder, on_delete=models.CASCADE, verbose_name=_("Recorder"), default=DEFAULT_RECORDER_ID, help_text=_("Recorder that made this recording."), ) user = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}, default=DEFAULT_RECORDER_USER_ID, help_text=_("User who has made the recording"), ) title = models.CharField(_("title"), max_length=200) type = models.CharField( _("Recording Type"), max_length=50, choices=RECORDER_TYPE, default=RECORDER_TYPE[0][0], ) source_file = models.FilePathField( max_length=200, path=DEFAULT_RECORDER_PATH, unique=True, recursive=True ) comment = models.TextField(_("Comment"), blank=True, default="") date_added = models.DateTimeField("date added", default=timezone.now, editable=False) @property def sites(self): return self.recorder.sites def __str__(self): return "%s" % self.title class Meta: verbose_name = _("Recording") verbose_name_plural = _("Recordings") def save(self, *args, **kwargs): super(Recording, self).save(*args, **kwargs) def clean(self): msg = list() msg = self.verify_attributs() if len(msg) > 0: raise ValidationError(msg) def verify_attributs(self): msg = list() if not self.type: msg.append(_("Please specify a type.")) elif self.type not in dict(RECORDER_TYPE): msg.append(_("Please use the only type in type choices.")) if not self.source_file: msg.append(_("Please specify a source file.")) elif not os.path.exists(self.source_file): msg.append(_("Source file doesn't exists")) return msg
class UserFolder(models.Model): name = models.CharField(_("Name"), max_length=255) # parent = models.ForeignKey( # 'self', blank=True, null=True, related_name='children') owner = select2_fields.ForeignKey(User, verbose_name=_("Owner")) created_at = models.DateTimeField(auto_now_add=True) access_groups = select2_fields.ManyToManyField( "authentication.AccessGroup", blank=True, verbose_name=_("Groups"), help_text=_("Select one or more groups who" " can access in read only to this folder"), ) users = select2_fields.ManyToManyField( User, blank=True, verbose_name=_("Users"), related_name="shared_files", help_text=_("Select one or more users who" " can access in read only to this folder"), ) class Meta: unique_together = (("name", "owner"), ) verbose_name = _("User directory") verbose_name_plural = _("User directories") ordering = ["name"] app_label = "podfile" def clean(self): if self.name == "Home": same_home = UserFolder.objects.filter(owner=self.owner, name="Home") if same_home: raise ValidationError( "A user cannot have have multiple home directories.") def __str__(self): return "{0}".format(self.name) def get_all_files(self): file_list = self.customfilemodel_set.all() image_list = self.customimagemodel_set.all() result_list = sorted(chain(image_list, file_list), key=attrgetter("uploaded_at")) return result_list def delete(self): for file in self.customfilemodel_set.all(): file.delete() for img in self.customimagemodel_set.all(): img.delete() super(UserFolder, self).delete()
class Attendee(models.Model): # Meeting for which this user was a moderator meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, verbose_name=_("Meeting")) # Full name (First_name Last_name) of the user from BigBlueButton full_name = models.CharField( _("Full name"), max_length=200, help_text=_("Full name of the user from BBB."), ) # Role of this user (only MODERATOR for the moment) role = models.CharField( _("User role"), max_length=200, help_text=_("Role of the user from BBB."), ) # Username of this user, if the BBB user was translated with a Pod user # Redundant information with user, but can be useful. username = models.CharField( _("Username / User id"), max_length=150, help_text=_( "Username / User id, if the BBB user was matching a Pod user."), ) # Pod user, if the BBB user was translated with a Pod user user = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}, verbose_name=_("User"), null=True, blank=True, help_text=_("User from the Pod database, if user found."), ) def __unicode__(self): return "%s - %s" % (self.full_name, self.role) def __str__(self): return "%s - %s" % (self.full_name, self.role) def save(self, *args, **kwargs): super(Attendee, self).save(*args, **kwargs) class Meta: verbose_name = _("Attendee") verbose_name_plural = _("Attendees") ordering = ["full_name"]
class PlaylistElement(models.Model): playlist = select2_fields.ForeignKey(Playlist, verbose_name=_('Playlist')) video = select2_fields.ForeignKey(Video, verbose_name=_('Video')) position = models.PositiveSmallIntegerField( _('Position'), default=1, help_text=_('Position of the video in a playlist.')) class Meta: unique_together = ( 'playlist', 'video', ) ordering = ['position', 'id'] verbose_name = _('Playlist element') verbose_name_plural = _('Playlist elements') def clean(self): if self.video.is_draft: raise ValidationError( _('A video in draft mode cannot be added to a playlist.')) if self.video.password: raise ValidationError( _('A video with a password cannot be added to a playlist.')) def save(self, *args, **kwargs): self.full_clean() return super(PlaylistElement, self).save(*args, **kwargs) def delete(self): elements = PlaylistElement.objects.filter(playlist=self.playlist, position__gt=self.position) for element in elements: element.position = element.position - 1 element.save() super(PlaylistElement, self).delete()
class Document(models.Model): video = select2_fields.ForeignKey(Video, verbose_name=_('Video')) document = models.ForeignKey(CustomFileModel, null=True, blank=True, verbose_name=_('Document')) class Meta: verbose_name = _('Document') verbose_name_plural = _('Documents') @property def sites(self): return self.video.sites def clean(self): msg = list() msg = self.verify_document() + self.verify_not_same_document() if len(msg) > 0: raise ValidationError(msg) def verify_document(self): msg = list() if not self.document: msg.append(_('Please enter a document.')) if len(msg) > 0: return msg else: return list() def verify_not_same_document(self): msg = list() list_doc = Document.objects.filter(video=self.video) if self.id: list_doc = list_doc.exclude(id=self.id) if len(list_doc) > 0: for element in list_doc: if self.document == element.document: msg.append( _('This document is already contained ' + 'in the list')) if len(msg) > 0: return msg return list() def __str__(self): return u'Document: {0} - Video: {1}'.format(self.document.name, self.video)
class UserFolder(models.Model): name = models.CharField(_('Name'), max_length=255) # parent = models.ForeignKey( # 'self', blank=True, null=True, related_name='children') owner = select2_fields.ForeignKey(User, verbose_name=_('Owner')) created_at = models.DateTimeField(auto_now_add=True) groups = select2_fields.ManyToManyField( Group, blank=True, verbose_name=_('Groups'), help_text=_('Select one or more groups who' ' can access in read only to this folder')) users = select2_fields.ManyToManyField( User, blank=True, verbose_name=_('Users'), related_name="shared_files", help_text=_('Select one or more users who' ' can access in read only to this folder')) class Meta: unique_together = (('name', 'owner'),) verbose_name = _('User directory') verbose_name_plural = _('User directories') ordering = ['name'] app_label = 'podfile' def clean(self): if self.name == 'Home': same_home = UserFolder.objects.filter( owner=self.owner, name='Home') if same_home: raise ValidationError( 'A user cannot have have multiple home directories.') def __str__(self): return '{0}'.format(self.name) def get_all_files(self): file_list = self.customfilemodel_set.all() image_list = self.customimagemodel_set.all() result_list = sorted( chain(image_list, file_list), key=attrgetter('uploaded_at')) return result_list def delete(self): for file in self.customfilemodel_set.all(): file.delete() for img in self.customimagemodel_set.all(): img.delete() super(UserFolder, self).delete()
class User(models.Model): # User who performed the session in BigBlueButton meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, verbose_name=_('Meeting')) # Full name (First_name Last_name) of the user from BigBlueButton full_name = models.CharField(_('Full name'), max_length=200, help_text=_( 'Full name of the user from BBB.') ) # Role of this user (only MODERATOR for the moment) role = models.CharField(_('User role'), max_length=200, help_text=_( 'Role of the user from BBB.') ) # Username of this user, if the BBB user was translated with a Pod user # Redundant information with user, but can be useful. username = models.CharField(_('Username / User id'), max_length=150, help_text=_( 'Username / User id, if the BBB user was matching a Pod user.') ) # Pod user, if the BBB user was translated with a Pod user user = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={'is_staff': True}, verbose_name=_('User'), null=True, blank=True, help_text=_( 'User from the Pod database, if user found.') ) def __unicode__(self): return "%s - %s" % (self.full_name, self.role) def __str__(self): return "%s - %s" % (self.full_name, self.role) def save(self, *args, **kwargs): super(User, self).save(*args, **kwargs) class Meta: verbose_name = _("User") verbose_name_plural = _("Users") ordering = ['full_name']
class Contributor(models.Model): video = select2_fields.ForeignKey(Video, verbose_name=_('video')) name = models.CharField(_('lastname / firstname'), max_length=200) email_address = models.EmailField(_('mail'), null=True, blank=True, default='') role = models.CharField(_(u'role'), max_length=200, choices=ROLE_CHOICES, default='author') weblink = models.URLField(_(u'Web link'), max_length=200, null=True, blank=True) class Meta: verbose_name = _('Contributor') verbose_name_plural = _('Contributors') @property def sites(self): return self.video.sites def clean(self): msg = list() msg = self.verify_attributs() + self.verify_not_same_contributor() if len(msg) > 0: raise ValidationError(msg) def verify_attributs(self): msg = list() if not self.name or self.name == '': msg.append(_('Please enter a name.')) elif len(self.name) < 2 or len(self.name) > 200: msg.append(_('Please enter a name from 2 to 200 caracters.')) if self.weblink and len(self.weblink) > 200: msg.append( _('You cannot enter a weblink with more than 200 caracters.')) if not self.role: msg.append(_('Please enter a role.')) if len(msg) > 0: return msg else: return list() def verify_not_same_contributor(self): msg = list() list_contributor = Contributor.objects.filter(video=self.video) if self.id: list_contributor = list_contributor.exclude(id=self.id) if len(list_contributor) > 0: for element in list_contributor: if self.name == element.name and self.role == element.role: msg.append( _('There is already a contributor with the same ' + 'name and role in the list.')) return msg return list() def __str__(self): return u'Video:{0} - Name:{1} - Role:{2}'.format( self.video, self.name, self.role) def get_base_mail(self): data = base64.b64encode(self.email_address.encode()) return data def get_noscript_mail(self): return self.email_address.replace('@', "__AT__")
class Overlay(models.Model): POSITION_CHOICES = ( ('top-left', _(u'top-left')), ('top', _(u'top')), ('top-right', _(u'top-right')), ('right', _(u'right')), ('bottom-right', _(u'bottom-right')), ('bottom', _(u'bottom')), ('bottom-left', _(u'bottom-left')), ('left', _(u'left')), ) video = select2_fields.ForeignKey(Video, verbose_name=_('Video')) title = models.CharField(_('Title'), max_length=100) slug = models.SlugField( _('Slug'), unique=True, max_length=105, help_text=_('Used to access this instance, the "slug" is a short' + ' label containing only letters, numbers, underscore' + ' or dash top.'), editable=False) time_start = models.PositiveIntegerField( _('Start time'), default=1, help_text=_(u'Start time of the overlay, in seconds.')) time_end = models.PositiveIntegerField( _('End time'), default=2, help_text=_(u'End time of the overlay, in seconds.')) content = RichTextField(_('Content'), null=False, blank=False, config_name='complete') position = models.CharField(_('Position'), max_length=100, null=True, blank=False, choices=POSITION_CHOICES, default='bottom-right', help_text=_(u'Position of the overlay.')) background = models.BooleanField( _('Show background'), default=True, help_text=_(u'Show the background of the overlay.')) @property def sites(self): return self.video.sites class Meta: verbose_name = _('Overlay') verbose_name_plural = _('Overlays') ordering = ['time_start'] def clean(self): msg = list() msg += self.verify_title_items() msg += self.verify_time_items() msg += self.verify_overlap() if len(msg) > 0: raise ValidationError(msg) def verify_title_items(self): msg = list() if not self.title or self.title == '': msg.append(_('Please enter a title.')) elif len(self.title) < 2 or len(self.title) > 100: msg.append(_('Please enter a title from 2 to 100 characters.')) if len(msg) > 0: return msg else: return list() def verify_time_items(self): msg = list() if self.time_start > self.time_end: msg.append( _('The value of the time start field is greater than ' + 'the value of the end time field.')) elif self.time_end > self.video.duration: msg.append( _('The value of time end field is greater than the ' + 'video duration.')) elif self.time_start == self.time_end: msg.append( _('Time end field and time start field can\'t ' + 'be equal.')) if len(msg) > 0: return msg else: return list() def verify_overlap(self): msg = list() instance = None if self.slug: instance = Overlay.objects.get(slug=self.slug) list_overlay = Overlay.objects.filter(video=self.video) if instance: list_overlay = list_overlay.exclude(id=instance.id) if len(list_overlay) > 0: for element in list_overlay: if not (self.time_start < element.time_start and self.time_end <= element.time_start or self.time_start >= element.time_end and self.time_end > element.time_end): msg.append( _("There is an overlap with the overlay {0}," " please change time start and/or " "time end values.").format(element.title)) if len(msg) > 0: return msg return list() def save(self, *args, **kwargs): newid = -1 if not self.id: try: newid = get_nextautoincrement(Overlay) except Exception: try: newid = Overlay.objects.latest('id').id newid += 1 except Exception: newid = 1 else: newid = self.id newid = '{0}'.format(newid) self.slug = '{0}-{1}'.format(newid, slugify(self.title)) super(Overlay, self).save(*args, **kwargs) def __str__(self): return 'Overlay: {0} - Video: {1}'.format(self.title, self.video)
class Track(models.Model): video = select2_fields.ForeignKey(Video, verbose_name=_('Video')) kind = models.CharField(_('Kind'), max_length=10, choices=KIND_CHOICES, default='subtitles') lang = models.CharField(_('Language'), max_length=2, choices=LANG_CHOICES) src = models.ForeignKey(CustomFileModel, blank=True, null=True, verbose_name=_('Subtitle file')) @property def sites(self): return self.video.sites def get_label_lang(self): return "%s" % LANG_CHOICES_DICT[self.lang] class Meta: verbose_name = _('Track') verbose_name_plural = _('Tracks') def clean(self): msg = list() msg = self.verify_attributs() + self.verify_not_same_track() if len(msg) > 0: raise ValidationError(msg) def verify_attributs(self): msg = list() if not self.kind: msg.append(_('Please enter a kind.')) elif self.kind != 'subtitles' and self.kind != 'captions': msg.append(_('Please enter a correct kind.')) if not self.lang: msg.append(_('Please enter a language.')) if not self.src: msg.append(_('Please specify a track file.')) elif 'vtt' not in self.src.file_type: msg.append(_('Only ".vtt" format is allowed.')) if len(msg) > 0: return msg else: return list() def verify_not_same_track(self): msg = list() list_track = Track.objects.filter(video=self.video) if self.id: list_track = list_track.exclude(id=self.id) if len(list_track) > 0: for element in list_track: if self.kind == element.kind and self.lang == element.lang: msg.append( _('There is already a subtitle with the ' + 'same kind and language in the list.')) return msg return list() def __str__(self): return u'{0} - File: {1} - Video: {2}'.format(self.kind, self.src.name, self.video)
class Meeting(models.Model): # Meeting id for the BBB session meeting_id = models.CharField( _('Meeting id'), max_length=200, help_text=_('Id of the BBB meeting.') ) # Internal meeting id for the BBB session internal_meeting_id = models.CharField( _('Internal meeting id'), max_length=200, help_text=_('Internal id of the BBB meeting.') ) # Meeting name for the BBB session meeting_name = models.CharField( _('Meeting name'), max_length=200, help_text=_('Name of the BBB meeting.') ) # Date of the BBB session session_date = models.DateTimeField( _('Session date'), default=timezone.now) # Encoding step / status of the process. Possible values are : # - 0 : default (Publish is possible) # - 1 : Waiting for encoding # - 2 : Encoding in progress # - 3 : Already published encoding_step = models.IntegerField( _('Encoding step'), help_text=_('Encoding step for conversion of the ' 'BBB presentation to video file.'), default=0) # Is this meeting was recorded in BigBlueButton ? recorded = models.BooleanField( _('Recorded'), help_text=_('BBB presentation recorded ?'), default=False) # Is the recording of the presentation is available in BigBlueButton ? recording_available = models.BooleanField( _('Recording available'), help_text=_('BBB presentation recording is available ?'), default=False) # URL of the recording of the BigBlueButton presentation recording_url = models.CharField( _('Recording url'), help_text=_('URL of the recording of the BBB presentation.'), max_length=200 ) # URL of the recording thumbnail of the BigBlueButton presentation thumbnail_url = models.CharField( _('Thumbnail url'), help_text=_('URL of the recording thumbnail of the BBB presentation.'), max_length=200 ) # User who converted the BigBlueButton presentation to video file encoded_by = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={'is_staff': True}, verbose_name=_('User'), null=True, blank=True, help_text=_( 'User who converted the BBB presentation to video file.') ) def __unicode__(self): return "%s - %s" % (self.meeting_name, self.meeting_id) def __str__(self): return "%s - %s" % (self.meeting_name, self.meeting_id) def save(self, *args, **kwargs): super(Meeting, self).save(*args, **kwargs) class Meta: verbose_name = _("Meeting") verbose_name_plural = _("Meetings") ordering = ['session_date']
class Recorder(models.Model): # Recorder name name = models.CharField(_("name"), max_length=200, unique=True) # Description of the recorder description = RichTextField(_("description"), config_name="complete", blank=True) # IP address of the recorder address_ip = models.GenericIPAddressField( _("Address IP"), unique=True, help_text=_("IP address of the recorder.") ) # Salt for salt = models.CharField( _("salt"), max_length=50, blank=True, help_text=_("Recorder salt.") ) # Recording type (video, AUdioVideoCasst, etc) recording_type = models.CharField( _("Recording Type"), max_length=50, choices=RECORDER_TYPE, default=RECORDER_TYPE[0][0], ) # Manager of the recorder who received mails user = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}, help_text=_( "Manager of this recorder. This manager will receive recorder " "emails and he will be the owner of the published videos. If no " "user is selected, this recorder will use manual assign system." ), verbose_name=_("User"), null=True, blank=True, ) # Additionnal additional_users additional_users = select2_fields.ManyToManyField( User, blank=True, ajax=True, js_options={"width": "off"}, verbose_name=_("Additional users"), search_field=select_recorder_user(), related_name="users_recorders", help_text=_( "You can add additionals users to the recorder. They " "will become the additionnals owners of the published videos " "and will have the same rights as the owner except that they " "can't delete the published videos." ), ) # Default type of published videos by this recorder type = models.ForeignKey( Type, on_delete=models.CASCADE, help_text=_("Video type by default.") ) is_draft = models.BooleanField( verbose_name=_("Draft"), help_text=_( "If this box is checked, " "the video will be visible and accessible only by you " "and the additional owners." ), default=True, ) is_restricted = models.BooleanField( verbose_name=_("Restricted access"), help_text=_( "If this box is checked, " "the video will only be accessible to authenticated users." ), default=False, ) restrict_access_to_groups = select2_fields.ManyToManyField( Group, blank=True, verbose_name=_("Groups"), help_text=_("Select one or more groups who can access to this video"), ) password = models.CharField( _("password"), help_text=_("Viewing this video will not be possible without this password."), max_length=50, blank=True, null=True, ) cursus = models.CharField( _("University course"), max_length=1, choices=CURSUS_CODES, default="0", help_text=_("Select an university course as audience target of the content."), ) main_lang = models.CharField( _("Main language"), max_length=2, choices=LANG_CHOICES, default=get_language(), help_text=_("Select the main language used in the content."), ) transcript = models.BooleanField( _("Transcript"), default=False, help_text=_("Check this box if you want to transcript the audio. (beta version)"), ) tags = TagField( help_text=_( "Separate tags with spaces, " "enclose the tags consist of several words in quotation marks." ), verbose_name=_("Tags"), ) discipline = select2_fields.ManyToManyField( Discipline, blank=True, verbose_name=_("Disciplines") ) licence = models.CharField( _("Licence"), max_length=8, choices=LICENCE_CHOICES, blank=True, null=True, ) channel = select2_fields.ManyToManyField( Channel, verbose_name=_("Channels"), blank=True ) theme = models.ManyToManyField( Theme, verbose_name=_("Themes"), blank=True, help_text=_( 'Hold down "Control", or "Command" ' "on a Mac, to select more than one." ), ) allow_downloading = models.BooleanField( _("allow downloading"), default=False, help_text=_("Check this box if you to allow downloading of the encoded files"), ) is_360 = models.BooleanField( _("video 360"), default=False, help_text=_("Check this box if you want to use the 360 player for the video"), ) disable_comment = models.BooleanField( _("Disable comment"), help_text=_("Allows you to turn off all comments on this video."), default=False, ) # Directory name where videos of this recorder are published directory = models.CharField( _("Publication directory"), max_length=50, unique=True, help_text=_("Basic directory containing the videos published by the recorder."), ) sites = models.ManyToManyField(Site) def __unicode__(self): return "%s - %s" % (self.name, self.address_ip) def __str__(self): return "%s - %s" % (self.name, self.address_ip) def ipunder(self): return self.address_ip.replace(".", "_") def save(self, *args, **kwargs): super(Recorder, self).save(*args, **kwargs) class Meta: verbose_name = _("Recorder") verbose_name_plural = _("Recorders") ordering = ["name"]
class Livestream(models.Model): # Meeting meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, verbose_name=_("Meeting")) # Start date of the live start_date = models.DateTimeField( _("Start date"), default=timezone.now, help_text=_("Start date of the live."), ) # End date of the live end_date = models.DateTimeField( _("End date"), null=True, blank=True, help_text=_("End date of the live."), ) # Live status STATUS = ( (0, _("Live not started")), (1, _("Live in progress")), (2, _("Live stopped")), ) status = models.IntegerField(_("Live status"), choices=STATUS, default=0) # Server/Process performing the live server = models.CharField( _("Server"), max_length=20, null=True, blank=True, help_text=_("Server/process performing the live."), ) # User that want to perform the live user = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}, verbose_name=_("User"), null=True, blank=True, help_text=_("Username / User id, that want to perform the live."), ) # Restricted access to the created live is_restricted = models.BooleanField( verbose_name=_("Restricted access"), help_text=_("Is live only accessible to authenticated users?"), default=False, ) # Broadcaster in charge to perform the live broadcaster_id = models.IntegerField( null=True, blank=True, verbose_name=_("Broadcaster"), help_text=_("Broadcaster in charge to perform live."), ) # If the user wants that show the public chat in the live show_chat = models.BooleanField( verbose_name=_("Show public chat"), help_text=_("Do you want to show the public chat in the live?"), default=True, ) # If the user wants to download the video of this meeting after the live download_meeting = models.BooleanField( verbose_name=_("Save meeting in My videos"), help_text=_( "Do you want to save the video of " 'this meeting, at the end of the live, directly in "My videos"?'), default=False, ) # If the user wants that students have a chat in the live page enable_chat = models.BooleanField( verbose_name=_("Enable chat"), help_text=_( "Do you want a chat on the live page " "for students? Messages sent in this live page's chat will " "end up in BigBlueButton's public chat."), default=False, ) # Redis hostname, useful for chat redis_hostname = models.CharField( _("Redis hostname"), max_length=200, null=True, blank=True, help_text=_("Redis hostname, useful for chat"), ) # Redis port, useful for chat redis_port = models.IntegerField( _("Redis port"), null=True, blank=True, help_text=_("Redis port, useful for chat"), ) # Redis channel, useful for chat redis_channel = models.CharField( _("Redis channel"), max_length=200, null=True, blank=True, help_text=_("Redis channel, useful for chat"), ) def __unicode__(self): return "%s - %s" % (self.meeting, self.status) def __str__(self): return "%s - %s" % (self.meeting, self.status) def save(self, *args, **kwargs): super(Livestream, self).save(*args, **kwargs) class Meta: verbose_name = _("Livestream") verbose_name_plural = _("Livestreams") ordering = ["start_date"]
class Meeting(models.Model): # Meeting id for the BBB session meeting_id = models.CharField(_("Meeting id"), max_length=200, help_text=_("Id of the BBB meeting.")) # Internal meeting id for the BBB session internal_meeting_id = models.CharField( _("Internal meeting id"), max_length=200, help_text=_("Internal id of the BBB meeting."), ) # Meeting name for the BBB session meeting_name = models.CharField( _("Meeting name"), max_length=200, help_text=_("Name of the BBB meeting."), ) # Date of the BBB session session_date = models.DateTimeField(_("Session date"), default=timezone.now) # Encoding step / status of the process ENCODING_STEP = ( (0, _("Publish is possible")), (1, _("Waiting for encoding")), (2, _("Encoding in progress")), (3, _("Already published")), ) encoding_step = models.IntegerField( _("Encoding step"), choices=ENCODING_STEP, help_text=_("Encoding step for conversion of the " "BBB presentation to video file."), default=0, ) # Is this meeting was recorded in BigBlueButton? recorded = models.BooleanField(_("Recorded"), help_text=_("BBB presentation recorded?"), default=False) # Is the recording of the presentation is available in BigBlueButton? recording_available = models.BooleanField( _("Recording available"), help_text=_("BBB presentation recording is available?"), default=False, ) # URL of the recording of the BigBlueButton presentation recording_url = models.CharField( _("Recording url"), help_text=_("URL of the recording of the BBB presentation."), max_length=200, ) # URL of the recording thumbnail of the BigBlueButton presentation thumbnail_url = models.CharField( _("Thumbnail url"), help_text=_("URL of the recording thumbnail of the BBB presentation."), max_length=200, ) # User who converted the BigBlueButton presentation to video file encoded_by = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={"is_staff": True}, verbose_name=_("User"), null=True, blank=True, help_text=_("User who converted the BBB presentation to video file."), ) # Last date of the BBB session in progress last_date_in_progress = models.DateTimeField( _("Last date in progress"), default=timezone.now, help_text=_("Last date where BBB session was in progress."), ) def __unicode__(self): return "%s - %s" % (self.meeting_name, self.meeting_id) def __str__(self): return "%s - %s" % (self.meeting_name, self.meeting_id) def save(self, *args, **kwargs): super(Meeting, self).save(*args, **kwargs) class Meta: verbose_name = _("Meeting") verbose_name_plural = _("Meetings") ordering = ["session_date"]
class Recorder(models.Model): # Recorder name name = models.CharField(_('name'), max_length=200, unique=True) # Description of the recorder description = RichTextField(_('description'), config_name='complete', blank=True) # IP address of the recorder address_ip = models.GenericIPAddressField( _('Address IP'), unique=True, help_text=_('IP address of the recorder.')) # Salt for salt = models.CharField(_('salt'), max_length=50, blank=True, help_text=_('Recorder salt.')) # Manager of the recorder who received mails user = select2_fields.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={'is_staff': True}, help_text=_( 'Manager of this recorder. This manager will receive recorder ' 'emails and he will be the owner of the published videos. If no ' 'user is selected, this recorder will use manual assign system.'), verbose_name=_('User'), null=True, blank=True) # Default type of published videos by this recorder type = models.ForeignKey(Type, on_delete=models.CASCADE, help_text=_('Video type by default.')) recording_type = models.CharField(_('Recording Type'), max_length=50, choices=RECORDER_TYPE, default=RECORDER_TYPE[0][0]) # Directory name where videos of this recorder are published directory = models.CharField( _('Publication directory'), max_length=50, unique=True, help_text=_( 'Basic directory containing the videos published by the recorder.') ) sites = models.ManyToManyField(Site) def __unicode__(self): return "%s - %s" % (self.name, self.address_ip) def __str__(self): return "%s - %s" % (self.name, self.address_ip) def ipunder(self): return self.address_ip.replace(".", "_") def save(self, *args, **kwargs): super(Recorder, self).save(*args, **kwargs) class Meta: verbose_name = _("Recorder") verbose_name_plural = _("Recorders") ordering = ['name']
class Chapter(models.Model): video = select2_fields.ForeignKey(Video, verbose_name=_("video")) title = models.CharField(_("title"), max_length=100) slug = models.SlugField( _("slug"), unique=True, max_length=105, help_text=_( 'Used to access this instance, the "slug" is a short' + " label containing only letters, numbers, underscore" + " or dash top." ), editable=False, ) time_start = models.PositiveIntegerField( _("Start time"), default=0, help_text=_(u"Start time of the chapter, in seconds."), ) class Meta: verbose_name = _("Chapter") verbose_name_plural = _("Chapters") ordering = ["time_start"] unique_together = ( "title", "time_start", "video", ) def clean(self): msg = list() msg = self.verify_title_items() + self.verify_time() msg = msg + self.verify_overlap() if len(msg) > 0: raise ValidationError(msg) def verify_title_items(self): msg = list() if ( not self.title or self.title == "" or len(self.title) < 2 or len(self.title) > 100 ): msg.append(_("Please enter a title from 2 to 100 characters.")) if len(msg) > 0: return msg return list() def verify_time(self): msg = list() if ( self.time_start == "" or self.time_start < 0 or self.time_start >= self.video.duration ): msg.append( _( "Please enter a correct start field between 0 and " + "{0}".format(self.video.duration - 1) ) ) if len(msg) > 0: return msg return list() def verify_overlap(self): msg = list() instance = None if self.slug: instance = Chapter.objects.get(slug=self.slug) list_chapter = Chapter.objects.filter(video=self.video) if instance: list_chapter = list_chapter.exclude(id=instance.id) if len(list_chapter) > 0: for element in list_chapter: if self.time_start == element.time_start: msg.append( _( "There is an overlap with the chapter " + "{0}, please change start and/or " + "end values." ).format(element.title) ) if len(msg) > 0: return msg return list() def save(self, *args, **kwargs): newid = -1 if not self.id: try: newid = get_nextautoincrement(Chapter) except Exception: try: newid = Chapter.objects.latest("id").id newid += 1 except Exception: newid = 1 else: newid = self.id newid = "{0}".format(newid) self.slug = "{0}-{1}".format(newid, slugify(self.title)) super(Chapter, self).save(*args, **kwargs) @property def chapter_in_time(self): return time.strftime("%H:%M:%S", time.gmtime(self.time_start)) chapter_in_time.fget.short_description = _("Start time") @property def sites(self): return self.video.sites def __str__(self): return u"Chapter: {0} - video: {1}".format(self.title, self.video)
class Enrichment(models.Model): ENRICH_CHOICES = ( ('image', _("image")), ('richtext', _("richtext")), ('weblink', _("weblink")), ('document', _("document")), ('embed', _("embed")), ) video = select2_fields.ForeignKey(Video, verbose_name=_('video')) title = models.CharField(_('title'), max_length=100) slug = models.SlugField( _('slug'), unique=True, max_length=105, help_text=_('Used to access this instance, the "slug" is a short' + ' label containing only letters, numbers, underscore' + ' or dash top.'), editable=False) stop_video = models.BooleanField( _('Stop video'), default=False, help_text=_(u'The video will pause when displaying the enrichment.')) start = models.PositiveIntegerField( _('Start'), default=0, help_text=_('Start of enrichment display in seconds.')) end = models.PositiveIntegerField( _('End'), default=1, help_text=_('End of enrichment display in seconds.')) type = models.CharField(_('Type'), max_length=10, choices=ENRICH_CHOICES, null=True, blank=True) image = models.ForeignKey(CustomImageModel, verbose_name=_('Image'), null=True, blank=True) document = models.ForeignKey( CustomFileModel, verbose_name=_('Document'), null=True, blank=True, help_text=_(u'Integrate an document (PDF, text, html)')) richtext = RichTextField(_('Richtext'), config_name='complete', blank=True) weblink = models.URLField(_(u'Web link'), max_length=200, null=True, blank=True) embed = models.TextField(_('Embed'), null=True, blank=True, help_text=_(u'Integrate an external source.')) class Meta: verbose_name = _('Enrichment') verbose_name_plural = _('Enrichments') ordering = ['start'] @property def sites(self): return self.video.sites def clean(self): msg = list() msg = self.verify_all_fields() if len(msg) > 0: raise ValidationError(msg) msg = self.verify_end_start_item() + self.overlap() if len(msg) > 0: raise ValidationError(msg) def verify_type(self, type): typelist = { 'image': self.image, 'richtext': self.richtext, 'weblink': self.weblink, 'document': self.document, 'embed': self.embed } if type not in typelist: return _('Please enter a correct {0}.'.format(type)) def verify_all_fields(self): msg = list() if (not self.title or self.title == '' or len(self.title) < 2 or len(self.title) > 100): msg.append(_('Please enter a title from 2 to 100 characters.')) if (self.start is None or self.start == '' or self.start < 0 or self.start >= self.video.duration): msg.append( _('Please enter a correct start field between 0 and ' + '{0}.'.format(self.video.duration - 1))) if (not self.end or self.end == '' or self.end <= 0 or self.end > self.video.duration): msg.append( _('Please enter a correct end field between 1 and ' + '{0}.'.format(self.video.duration))) if self.type: if self.verify_type(self.type): msg.append(self.verify_type(self.type)) else: msg.append(_('Please enter a type.')) if len(msg) > 0: return msg else: return list() def verify_end_start_item(self): msg = list() video = Video.objects.get(id=self.video.id) if self.start > self.end: msg.append( _('The value of the start field is greater than the value ' + 'of end field.')) if self.end > video.duration: msg.append( _('The value of end field is greater than ' + 'the video duration.')) if self.end and self.start == self.end: msg.append(_('End field and start field can\'t be equal.')) if len(msg) > 0: return msg else: return list() def overlap(self): msg = list() instance = None if self.slug: instance = Enrichment.objects.get(slug=self.slug) list_enrichment = Enrichment.objects.filter(video=self.video) if instance: list_enrichment = list_enrichment.exclude(id=instance.id) if len(list_enrichment) > 0: for element in list_enrichment: if not ( (self.start < element.start and self.end <= element.start) or (self.start >= element.end and self.end > element.end)): msg.append( _("There is an overlap with the enrichment {0}," " please change time start and/or " "time end values.").format(element.title)) if len(msg) > 0: return msg return list() def save(self, *args, **kwargs): newid = -1 if not self.id: try: newid = get_nextautoincrement(Enrichment) except Exception: try: newid = Enrichment.objects.latest('id').id newid += 1 except Exception: newid = 1 else: newid = self.id newid = '{0}'.format(newid) self.slug = '{0} - {1}'.format(newid, slugify(self.title)) super(Enrichment, self).save(*args, **kwargs) def __str__(self): return u'Media : {0} - Video: {1}'.format(self.title, self.video)
class Playlist(models.Model): title = models.CharField(_('Title'), max_length=100, unique=True) slug = models.SlugField( _('Slug'), unique=True, max_length=100, help_text=_('Used to access this instance, the "slug" is a short' + ' label containing only letters, numbers, underscore' + ' or dash top.')) owner = select2_fields.ForeignKey(User, verbose_name=_('Owner')) description = models.TextField( _('Description'), max_length=255, null=True, blank=True, help_text=_('Short description of the playlist.')) visible = models.BooleanField( _('Visible'), default=False, help_text=_('If checked, the playlist can be visible to users' + ' other than the owner.')) class Meta: ordering = ['title', 'id'] verbose_name = _('Playlist') verbose_name_plural = _('Playlists') def save(self, *args, **kwargs): newid = -1 if not self.id: try: newid = get_nextautoincrement(Playlist) except Exception: try: newid = Playlist.objects.latest('id').id newid += 1 except Exception: newid = 1 else: newid = self.id newid = '{0}'.format(newid) self.slug = '{0}-{1}'.format(newid, slugify(self.title)) super(Playlist, self).save(*args, **kwargs) def __str__(self): return '{0}'.format(self.title) def first(self): return PlaylistElement.objects.get(playlist=self, position=1) def last(self): last = PlaylistElement.objects.filter( playlist=self).order_by('-position') if last: return last[0].position + 1 else: return 1 def videos(self): videos = list() elements = PlaylistElement.objects.filter(playlist=self) for element in elements: videos.append(element.video) return videos
class Broadcaster(models.Model): name = models.CharField(_('name'), max_length=200, unique=True) slug = models.SlugField( _('Slug'), unique=True, max_length=200, help_text=_( u'Used to access this instance, the "slug" is a short label ' + 'containing only letters, numbers, underscore or dash top.'), editable=False, default="") # default empty, fill it in save building = models.ForeignKey('Building', verbose_name=_('Building')) description = RichTextField(_('description'), config_name='complete', blank=True) poster = models.ForeignKey(CustomImageModel, models.SET_NULL, blank=True, null=True, verbose_name=_('Poster')) url = models.URLField(_('URL'), help_text=_('Url of the stream'), unique=True) video_on_hold = select2_fields.ForeignKey( Video, help_text=_( 'This video will be displayed when there is no live stream.'), blank=True, null=True, verbose_name=_('Video on hold')) iframe_url = models.URLField( _('Embedded Site URL'), help_text=_('Url of the embedded site to display'), null=True, blank=True) iframe_height = models.IntegerField( _('Embedded Site Height'), null=True, blank=True, help_text=_('Height of the embedded site (in pixels)')) status = models.BooleanField( default=0, help_text=_('Check if the broadcaster is currently sending stream.')) enable_viewer_count = models.BooleanField( default=1, verbose_name=_(u'Enable viewers count'), help_text=_('Enable viewers count on live.')) is_restricted = models.BooleanField( verbose_name=_(u'Restricted access'), help_text=_('Live is accessible only to authenticated users.'), default=False) public = models.BooleanField( verbose_name=_(u'Show in live tab'), help_text=_('Live is accessible from the Live tab'), default=True) password = models.CharField( _('password'), help_text=_( 'Viewing this live will not be possible without this password.'), max_length=50, blank=True, null=True) viewcount = models.IntegerField(_('Number of viewers'), default=0, editable=False) viewers = models.ManyToManyField(User, editable=False) def get_absolute_url(self): return reverse('live:video_live', args=[str(self.slug)]) def __str__(self): return "%s - %s" % (self.name, self.url) def get_poster_url(self): if self.poster: return self.poster.file.url else: thumbnail_url = ''.join([settings.STATIC_URL, DEFAULT_THUMBNAIL]) return thumbnail_url def save(self, *args, **kwargs): self.slug = slugify(self.name) super(Broadcaster, self).save(*args, **kwargs) class Meta: verbose_name = _("Broadcaster") verbose_name_plural = _("Broadcasters") ordering = ['building', 'name'] @property def sites(self): return self.building.sites
class Overlay(models.Model): POSITION_CHOICES = ( ("top-left", _("top-left")), ("top", _("top")), ("top-right", _("top-right")), ("right", _("right")), ("bottom-right", _("bottom-right")), ("bottom", _("bottom")), ("bottom-left", _("bottom-left")), ("left", _("left")), ) video = select2_fields.ForeignKey(Video, verbose_name=_("Video")) title = models.CharField(_("Title"), max_length=100) slug = models.SlugField( _("Slug"), unique=True, max_length=105, help_text=_('Used to access this instance, the "slug" is a short' + " label containing only letters, numbers, underscore" + " or dash top."), editable=False, ) time_start = models.PositiveIntegerField( _("Start time"), default=1, help_text=_("Start time of the overlay, in seconds."), ) time_end = models.PositiveIntegerField( _("End time"), default=2, help_text=_("End time of the overlay, in seconds."), ) content = RichTextField(_("Content"), null=False, blank=False, config_name="complete") position = models.CharField( _("Position"), max_length=100, null=True, blank=False, choices=POSITION_CHOICES, default="bottom-right", help_text=_("Position of the overlay."), ) background = models.BooleanField( _("Show background"), default=True, help_text=_("Show the background of the overlay."), ) @property def sites(self): return self.video.sites class Meta: verbose_name = _("Overlay") verbose_name_plural = _("Overlays") ordering = ["time_start"] def clean(self): msg = list() msg += self.verify_title_items() msg += self.verify_time_items() msg += self.verify_overlap() if len(msg) > 0: raise ValidationError(msg) def verify_title_items(self): msg = list() if not self.title or self.title == "": msg.append(_("Please enter a title.")) elif len(self.title) < 2 or len(self.title) > 100: msg.append(_("Please enter a title from 2 to 100 characters.")) if len(msg) > 0: return msg else: return list() def verify_time_items(self): msg = list() if self.time_start > self.time_end: msg.append( _("The value of the time start field is greater than " + "the value of the end time field.")) elif self.time_end > self.video.duration: msg.append( _("The value of time end field is greater than the " + "video duration.")) elif self.time_start == self.time_end: msg.append( _("Time end field and time start field can't " + "be equal.")) if len(msg) > 0: return msg else: return list() def verify_overlap(self): msg = list() instance = None if self.slug: instance = Overlay.objects.get(slug=self.slug) list_overlay = Overlay.objects.filter(video=self.video) if instance: list_overlay = list_overlay.exclude(id=instance.id) if len(list_overlay) > 0: for element in list_overlay: if not (self.time_start < element.time_start and self.time_end <= element.time_start or self.time_start >= element.time_end and self.time_end > element.time_end): msg.append( _("There is an overlap with the overlay {0}," " please change time start and/or " "time end values.").format(element.title)) if len(msg) > 0: return msg return list() def save(self, *args, **kwargs): newid = -1 if not self.id: try: newid = get_nextautoincrement(Overlay) except Exception: try: newid = Overlay.objects.latest("id").id newid += 1 except Exception: newid = 1 else: newid = self.id newid = "{0}".format(newid) self.slug = "{0}-{1}".format(newid, slugify(self.title)) super(Overlay, self).save(*args, **kwargs) def __str__(self): return "Overlay: {0} - Video: {1}".format(self.title, self.video)