class Subscription(models.Model): project = models.ForeignKey(Project, related_name='subscriptions', on_delete=models.CASCADE) email = models.CharField(max_length=1024, null=True, blank=True, validators=[EmailValidator()]) user = models.ForeignKey( User, null=True, blank=True, default=None, on_delete=models.CASCADE, ) NOTIFY_ALL_BUILDS = 'all' NOTIFY_ON_CHANGE = 'change' NOTIFY_ON_REGRESSION = 'regression' STRATEGY_CHOICES = ( (NOTIFY_ALL_BUILDS, N_("All builds")), (NOTIFY_ON_CHANGE, N_("Only on change")), (NOTIFY_ON_REGRESSION, N_("Only on regression")), ) notification_strategy = models.CharField(max_length=32, choices=STRATEGY_CHOICES, default='all') class Meta: unique_together = ( 'project', 'user', ) def _validate_email(self): if (not self.email) == (not self.user): raise ValidationError( "Subscription object must have exactly one of 'user' and 'email' fields populated." ) def save(self, *args, **kwargs): self._validate_email() super().save(*args, **kwargs) def clean(self): self._validate_email() def get_email(self): if self.user and self.user.email: return self.user.email return self.email def __str__(self): return '%s on %s' % (self.get_email(), self.project)
class Group(models.Model, DisplayName): objects = GroupManager() slug = models.CharField(max_length=100, unique=True, validators=[group_slug_validator], db_index=True, verbose_name=N_('Slug')) valid_slug_pattern = slug_pattern name = models.CharField(max_length=100, null=True, blank=True, verbose_name=N_('Name')) description = models.TextField(null=True, blank=True, verbose_name=N_('Description')) members = models.ManyToManyField(User, through='GroupMember', verbose_name=N_('Members')) def add_user(self, user, access=None): member = GroupMember(group=self, user=user) if access: member.access = access member.save() def add_admin(self, user): self.add_user(user, 'admin') def accessible_to(self, user): return GroupMember.objects.filter(group=self, user=user.id).exists() or self.writable_by(user) def can_submit(self, user): return user.is_superuser or user.is_staff or self.has_access(user, 'admin', 'submitter') def writable_by(self, user): return user.is_superuser or user.is_staff or self.has_access(user, 'admin') def has_access(self, user, *access_levels): return GroupMember.objects.filter( group=self, user=user.id, access__in=access_levels ).exists() def __str__(self): return self.slug def full_clean(self, **kwargs): errors = {} try: super().full_clean(**kwargs) except ValidationError as e: errors = e.update_error_dict(errors) if self.slug and not re.match(self.valid_slug_pattern, self.slug): errors['slug'] = [_('Enter a valid value.')] if errors: raise ValidationError(errors) class Meta: ordering = ['slug']
class GroupMember(models.Model): ACCESS_LEVELS = ( ('member', N_('Member')), ('submitter', N_('Result submitter')), ('admin', N_('Administrator')), ) user = models.ForeignKey(User, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) access = models.CharField(max_length=10, choices=ACCESS_LEVELS, default='member') member_since = models.DateField(auto_now_add=True) class Meta: unique_together = ('group', 'user') verbose_name = N_('Group member') verbose_name_plural = N_('Group members')
class EmailTemplate(models.Model): name = models.CharField(max_length=100, unique=True) subject = models.CharField( max_length=1024, null=True, blank=True, help_text=N_('Jinja2 template for subject (single line)'), validators=[jinja2_validator]) plain_text = models.TextField(help_text=N_('Jinja2 template for text/plain content'), validators=[jinja2_validator]) html = models.TextField(blank=True, null=True, help_text=N_('Jinja2 template for text/html content'), validators=[jinja2_validator]) # If any of the attributes need not to be tracked, just pass excluded_fields=['attr'] history = HistoricalRecords(cascade_delete_history=True) def __str__(self): return self.name
def clean(self): cleaned_data = super().clean() if cleaned_data['confirmation'] != self.deletable.slug: self.add_error('confirmation', N_(self.no_match_message)) return cleaned_data
def _time_remaining(self, secs_left): """Returns a string representing the time remaining for the operation. This is a simplified version of Django's timeuntil() function that does fewer calculations in order to reduce the amount of time we have to spend every loop. For instance, it doesn't bother with constructing datetimes and recomputing deltas, since we already have those, and it doesn't rebuild the TIME_REMAINING_CHUNKS every time it's called. It also handles seconds. """ delta = timedelta(seconds=secs_left) since = delta.days * 24 * 60 * 60 + delta.seconds if since < 60: return N_('%d second', '%d seconds') % since for i, (seconds, name) in enumerate(self.TIME_REMAINING_CHUNKS): count = since // seconds if count != 0: break result = name % count if i + 1 < len(self.TIME_REMAINING_CHUNKS): seconds2, name2 = self.TIME_REMAINING_CHUNKS[i + 1] count2 = (since - (seconds * count)) // seconds2 if count2 != 0: result += ', ' + name2 % count2 return result
class TestjobFilter(django_filters.FilterSet): has_errors = django_filters.BooleanFilter(field_name='failure', lookup_expr='isnull', label=N_('Success')) name = django_filters.CharFilter(field_name='name', lookup_expr='icontains', label=N_('Name')) job_status = django_filters.CharFilter(field_name='job_status', lookup_expr='icontains', label=N_('Job Status')) submitted = django_filters.BooleanFilter(field_name='submitted', lookup_expr='exact', label=N_('Is submitted')) fetched = django_filters.BooleanFilter(field_name='fetched', lookup_expr='exact', label=N_('Is Fetched')) job_id = django_filters.CharFilter(field_name='job_id', lookup_expr='icontains', label=N_('Job ID')) environment = django_filters.CharFilter(field_name='environment', lookup_expr='icontains', label=N_('Environment')) class Meta: model = TestJob fields = [ 'name', 'job_status', 'submitted', 'fetched', 'job_id', 'environment' ]
class DeleteConfirmationForm(forms.Form): confirmation = forms.CharField( label=N_('Type the slug (the name used in URLs) to confirm')) label = None no_match_message = N_('The confirmation does not match the slug') def __init__(self, *args, **kwargs): self.deletable = kwargs.pop('deletable') super().__init__(*args, **kwargs) if self.label: self.fields['confirmation'].label = N_(self.label) def clean(self): cleaned_data = super().clean() if cleaned_data['confirmation'] != self.deletable.slug: self.add_error('confirmation', N_(self.no_match_message)) return cleaned_data
def BlocklyPlanetsCodesView(request): FirstSaveTag = "firstSave" pid = request.session["PlayerID"] service = createEngineClient() player = None message = "" errorMessage = "" if request.method == "POST": if "save_button_error" in request.POST and len(request.POST["save_button_error"]): errorMessage = request.POST["save_button_error"] logger = logging.getLogger(__name__) logger.error("javascript error when saving blockly code : " + message); elif "blocklyXML" in request.POST: service.setPlayerPlanetCode(pid, request.POST["scriptXML"].encode("utf8")) service.setPlayerPlanetBlocklyCode(pid, request.POST["blocklyXML"].encode("utf8")) player = service.getPlayer(pid) message = _("Code successfully saved") firstSave = player.tutoDisplayed.get(FirstSaveTag, 0); if firstSave == 0: service.incrementTutoDisplayed(pid, FirstSaveTag, 1) message = _("See in planets tab if the building is in progress") if player == None: player = service.getPlayer(pid) plLvl = player.tutoDisplayed.get(CoddingLevelTag, 0) codeData = player.planetsCode tutosText = N_("BLOCKLY_TUTO_" + str(plLvl)) if plLvl <= 8 else None if not CodeViewTutoTag in player.tutoDisplayed: helpMessage = _("CODE_TUTOS") service.incrementTutoDisplayed(pid, CodeViewTutoTag, 1) else: helpMessage = None timeInfo = service.getTimeInfo() tutoInfoIsSeen = checkIfTutoInfoSeen(service, player) return render(request, 'codesview/blockly_planet.html', { "name": "Planet", "level": plLvl, "message": message, "errorMessage": errorMessage, "codeData": codeData, "tutosText": tutosText, "mode": "blockly", "helpMessage": helpMessage, "timeInfo": timeInfo, "tutoInfoIsSeen": tutoInfoIsSeen, 'player':player, })
def BlocklyFleetsCodesView(request): pid = request.session["PlayerID"] service = createEngineClient() message = "" errorMessage = "" if request.method == "POST": if "save_button_error" in request.POST and len(request.POST["save_button_error"]): errorMessage = request.POST["save_button_error"] logger = logging.getLogger(__name__) logger.error("javascript error when saving blockly code : " + message); elif "blocklyXML" in request.POST: service.setPlayerFleetCode(pid, request.POST["scriptXML"].encode("utf8")) service.setPlayerFleetBlocklyCode(pid, request.POST["blocklyXML"].encode("utf8")) message = _("Code successfully saved") player = service.getPlayer(pid) codeData = player.fleetsCode plLvl = player.tutoDisplayed.get(CoddingLevelTag, 0) tutosText = N_("BLOCKLY_TUTO_" + str(plLvl)) if plLvl <= 8 else None timeInfo = service.getTimeInfo() tutoInfoIsSeen = checkIfTutoInfoSeen(service, player) return render(request, 'codesview/blockly_fleet.html', { "name": "Fleet", "level": plLvl, "message": message, "errorMessage": errorMessage, "codeData": codeData, "tutosText": tutosText, "mode": "blockly", 'timeInfo': timeInfo, 'tutoInfoIsSeen': tutoInfoIsSeen, 'player':player, })
def __init__(self, *args, **kwargs): self.deletable = kwargs.pop('deletable') super().__init__(*args, **kwargs) if self.label: self.fields['confirmation'].label = N_(self.label)
class Project(models.Model, DisplayName): objects = ProjectManager() group = models.ForeignKey(Group, related_name='projects') slug = models.CharField(max_length=100, validators=[slug_validator], db_index=True, verbose_name=N_('Slug')) name = models.CharField(max_length=100, null=True, blank=True, verbose_name=N_('Name')) is_public = models.BooleanField(default=True, verbose_name=N_('Is public')) html_mail = models.BooleanField(default=True) moderate_notifications = models.BooleanField(default=False) custom_email_template = models.ForeignKey(EmailTemplate, null=True, blank=True) description = models.TextField(null=True, blank=True, verbose_name=N_('Description')) important_metadata_keys = models.TextField(null=True, blank=True) enabled_plugins_list = PluginListField( null=True, blank=True, features=[ Plugin.postprocess_testrun, Plugin.postprocess_testjob, ], ) wait_before_notification = models.IntegerField( help_text=N_('Wait this many seconds before sending notifications'), null=True, blank=True, ) notification_timeout = models.IntegerField( help_text=N_( 'Force sending build notifications after this many seconds'), null=True, blank=True, ) data_retention_days = models.IntegerField( default=0, help_text=N_( "Delete builds older than this number of days. Set to 0 or any negative number to disable." ), ) project_settings = models.TextField(null=True, blank=True, validators=[yaml_validator]) is_archived = models.BooleanField( default=False, help_text=N_( 'Makes the project hidden from the group page by default'), verbose_name=N_('Is archived'), ) def __init__(self, *args, **kwargs): super(Project, self).__init__(*args, **kwargs) self.__status__ = None @property def status(self): if not self.__status__: try: self.__status__ = ProjectStatus.objects.filter( build__project=self).latest('created_at') except ProjectStatus.DoesNotExist: pass return self.__status__ def accessible_to(self, user): return self.is_public or self.group.accessible_to(user) def can_submit(self, user): return self.group.can_submit(user) def writable_by(self, user): return self.group.writable_by(user) @property def full_name(self): return str(self.group) + '/' + self.slug def __str__(self): return self.full_name class Meta: unique_together = ( 'group', 'slug', ) ordering = ['group', 'slug'] @property def enabled_plugins(self): return self.enabled_plugins_list
class Meta: unique_together = ('group', 'user') verbose_name = N_('Group member') verbose_name_plural = N_('Group members')
class DeleteGroupForm(DeleteConfirmationForm): label = N_('Type the group slug (the name used in URLs) to confirm') no_match_message = N_('The confirmation does not match the group slug')
class Command(NoArgsCommand): help = ('Condenses the diffs stored in the database, reducing space ' 'requirements') DELAY_SHOW_REMAINING_SECS = 30 TIME_REMAINING_CHUNKS = ((60 * 60 * 24 * 365, N_('%d year', '%d years')), (60 * 60 * 24 * 30, N_('%d month', '%d months')), (60 * 60 * 24 * 7, N_('%d week', '%d weeks')), (60 * 60 * 24, N_('%d day', '%d days')), (60 * 60, N_('%d hour', '%d hours')), (60, N_('%d minute', '%d minutes'))) # We add a bunch of spaces in order to override any previous # content on the line, for when it shrinks. TIME_REMAINING_STR = _('%s remaining ') CALC_TIME_REMAINING_STR = _('Calculating time remaining') def handle_noargs(self, **options): counts = FileDiff.objects.get_migration_counts() total_count = counts['total_count'] if total_count == 0: self.stdout.write(_('All diffs have already been migrated.\n')) return self.stdout.write( _('Processing %(count)d diffs for duplicates...\n' '\n' 'This may take a while. It is safe to continue using ' 'Review Board while this is\n' 'processing, but it may temporarily run slower.\n' '\n') % {'count': total_count}) # Don't allow queries to be stored. settings.DEBUG = False self.start_time = datetime.now() self.prev_prefix_len = 0 self.prev_time_remaining_s = '' self.show_remaining = False info = FileDiff.objects.migrate_all(self._on_batch_done, counts) old_diff_size = info['old_diff_size'] new_diff_size = info['new_diff_size'] self.stdout.write( _('\n' '\n' 'Condensed stored diffs from %(old_size)s bytes to ' '%(new_size)s bytes (%(savings_pct)0.2f%% savings)\n') % { 'old_size': intcomma(old_diff_size), 'new_size': intcomma(new_diff_size), 'savings_pct': (float(old_diff_size - new_diff_size) / float(old_diff_size) * 100), }) def _on_batch_done(self, processed_count, total_count): """Handler for when a batch of diffs are processed. This will report the progress of the operation, showing the estimated amount of time remaining. """ pct = processed_count * 100 / total_count delta = datetime.now() - self.start_time # XXX: This can be replaced with total_seconds() once we no longer have # to support Python 2.6 delta_secs = ((delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6) if (not self.show_remaining and delta_secs >= self.DELAY_SHOW_REMAINING_SECS): self.show_remaining = True if self.show_remaining: secs_left = ((delta_secs // processed_count) * (total_count - processed_count)) time_remaining_s = (self.TIME_REMAINING_STR % self._time_remaining(secs_left)) else: time_remaining_s = self.CALC_TIME_REMAINING_STR prefix_s = ' [%d%%] %s/%s - ' % (pct, processed_count, total_count) # NOTE: We use sys.stdout here instead of self.stderr in order # to control newlines. Command.stderr will force a \n for # each write. sys.stdout.write(prefix_s) # Only write out the time remaining string if it has changed or # there's been a shift in the length of the prefix. This reduces # how much we have to write to the terminal, and how often, by # a fair amount. if (self.prev_prefix_len != len(prefix_s) or self.prev_time_remaining_s != time_remaining_s): # Something has changed, so output the string and then cache # the values for the next call. sys.stdout.write(time_remaining_s) self.prev_prefix_len = len(prefix_s) self.prev_time_remaining_s = time_remaining_s sys.stdout.write('\r') sys.stdout.flush() def _time_remaining(self, secs_left): """Returns a string representing the time remaining for the operation. This is a simplified version of Django's timeuntil() function that does fewer calculations in order to reduce the amount of time we have to spend every loop. For instance, it doesn't bother with constructing datetimes and recomputing deltas, since we already have those, and it doesn't rebuild the TIME_REMAINING_CHUNKS every time it's called. It also handles seconds. """ delta = timedelta(seconds=secs_left) since = delta.days * 24 * 60 * 60 + delta.seconds if since < 60: return N_('%d second', '%d seconds') % since for i, (seconds, name) in enumerate(self.TIME_REMAINING_CHUNKS): count = since // seconds if count != 0: break result = name % count if i + 1 < len(self.TIME_REMAINING_CHUNKS): seconds2, name2 = self.TIME_REMAINING_CHUNKS[i + 1] count2 = (since - (seconds * count)) // seconds2 if count2 != 0: result += ', ' + name2 % count2 return result
class Command(BaseCommand): """Management command to condense stored diffs in the database.""" help = _('Condenses the diffs stored in the database, reducing space ' 'requirements') DELAY_SHOW_REMAINING_SECS = 30 TIME_REMAINING_CHUNKS = ((60 * 60 * 24 * 365, N_('%d year', '%d years')), (60 * 60 * 24 * 30, N_('%d month', '%d months')), (60 * 60 * 24 * 7, N_('%d week', '%d weeks')), (60 * 60 * 24, N_('%d day', '%d days')), (60 * 60, N_('%d hour', '%d hours')), (60, N_('%d minute', '%d minutes'))) # We add a bunch of spaces in order to override any previous # content on the line, for when it shrinks. TIME_REMAINING_STR = _('%s remaining ') CALC_TIME_REMAINING_STR = _('Calculating time remaining') def add_arguments(self, parser): """Add arguments to the command. Args: parser (argparse.ArgumentParser): The argument parser for the command. """ parser.add_argument( '--show-counts-only', action='store_true', dest='show_counts', default=False, help=_("Show the number of diffs that are expected to be " "migrated, but don't perform a migration.")) parser.add_argument( '--no-progress', action='store_false', dest='show_progress', default=True, help=_("Don't show progress information or totals while " "migrating. You might want to use this if your database " "is taking too long to generate total migration counts.")) parser.add_argument( '--max-diffs', action='store', dest='max_diffs', type=int, default=None, help=_("The maximum number of migrations to perform. This is " "useful if you have a lot of diffs to migrate and want " "to do it over several sessions.")) def handle(self, **options): """Handle the command. Args: **options (dict, unused): Options parsed on the command line. Raises: django.core.management.CommandError: There was an error performing a diff migration. """ self.show_progress = options['show_progress'] max_diffs = options['max_diffs'] if options['show_counts']: counts = FileDiff.objects.get_migration_counts() self.stdout.write( _('%d unmigrated Review Board pre-1.7 diffs\n') % counts['filediffs']) self.stdout.write( _('%d unmigrated Review Board 1.7-2.5 diffs\n') % counts['legacy_file_diff_data']) self.stdout.write( _('%d total unmigrated diffs\n') % counts['total_count']) return elif self.show_progress: counts = FileDiff.objects.get_migration_counts() total_count = counts['total_count'] if total_count == 0: self.stdout.write(_('All diffs have already been migrated.\n')) return warning = counts.get('warning') if warning: self.stderr.write(_('Warning: %s\n\n') % warning) if max_diffs is None: process_count = total_count else: process_count = min(max_diffs, total_count) self.stdout.write( _('Processing %(count)d unmigrated diffs...\n') % {'count': process_count}) else: # Set to an empty dictionary to force migrate_all() to not # look up its own counts. counts = {} self.stdout.write(_('Processing all unmigrated diffs...\n')) self.stdout.write( _('\n' 'This may take a while. It is safe to continue using ' 'Review Board while this is\n' 'processing, but it may temporarily run slower.\n' '\n')) # Don't allow queries to be stored. settings.DEBUG = False self.start_time = datetime.now() self.prev_prefix_len = 0 self.prev_time_remaining_s = '' self.show_remaining = False info = FileDiff.objects.migrate_all(batch_done_cb=self._on_batch_done, counts=counts, max_diffs=max_diffs) if info['diffs_migrated'] == 0: self.stdout.write(_('All diffs have already been migrated.\n')) else: old_diff_size = info['old_diff_size'] new_diff_size = info['new_diff_size'] self.stdout.write( _('\n' '\n' 'Condensed stored diffs from %(old_size)s bytes to ' '%(new_size)s bytes (%(savings_pct)0.2f%% savings)\n') % { 'old_size': intcomma(old_diff_size), 'new_size': intcomma(new_diff_size), 'savings_pct': (float(old_diff_size - new_diff_size) / float(old_diff_size) * 100), }) def _on_batch_done(self, total_diffs_migrated, total_count=None, **kwargs): """Handler for when a batch of diffs are processed. This will report the progress of the operation, showing the estimated amount of time remaining. Args: total_diffs_migrated (int): The total number of diffs migrated so far in this condensediffs operation. total_count (int, optional): The total number of diffs to migrate in the database. This may be ``None``, in which case the output won't contain progress and time estimation. **kwargs (dict, unused): Unused keyword arguments. """ # NOTE: We use sys.stdout when writing instead of self.stderr in order # to control newlines. Command.stderr will force a \n for each # write. if self.show_progress: # We may be receiving an estimate for the total number of diffs # that is less than the actual count. If we've gone past the # initial total, just bump up the total to the current count. total_count = max(total_diffs_migrated, total_count) pct = total_diffs_migrated * 100 / total_count delta = datetime.now() - self.start_time delta_secs = delta.total_seconds() if (not self.show_remaining and delta_secs >= self.DELAY_SHOW_REMAINING_SECS): self.show_remaining = True if self.show_remaining: secs_left = ((delta_secs // total_diffs_migrated) * (total_count - total_diffs_migrated)) time_remaining_s = (self.TIME_REMAINING_STR % self._time_remaining(secs_left)) else: time_remaining_s = self.CALC_TIME_REMAINING_STR prefix_s = ' [%d%%] %s/%s - ' % (pct, total_diffs_migrated, total_count) sys.stdout.write(prefix_s) # Only write out the time remaining string if it has changed or # there's been a shift in the length of the prefix. This reduces # how much we have to write to the terminal, and how often, by # a fair amount. if (self.prev_prefix_len != len(prefix_s) or self.prev_time_remaining_s != time_remaining_s): # Something has changed, so output the string and then cache # the values for the next call. sys.stdout.write(time_remaining_s) self.prev_prefix_len = len(prefix_s) self.prev_time_remaining_s = time_remaining_s else: sys.stdout.write(' %s diffs migrated' % total_diffs_migrated) sys.stdout.write('\r') sys.stdout.flush() def _time_remaining(self, secs_left): """Return a string representing the time remaining for the operation. This is a simplified version of Django's timeuntil() function that does fewer calculations in order to reduce the amount of time we have to spend every loop. For instance, it doesn't bother with constructing datetimes and recomputing deltas, since we already have those, and it doesn't rebuild the TIME_REMAINING_CHUNKS every time it's called. It also handles seconds. Args: secs_left (int): The estimated number of seconds remaining. Returns: unicode: The text containing the time remaining. """ delta = timedelta(seconds=secs_left) since = delta.days * 24 * 60 * 60 + delta.seconds if since < 60: return N_('%d second', '%d seconds') % since for i, (seconds, name) in enumerate(self.TIME_REMAINING_CHUNKS): count = since // seconds if count != 0: break result = name % count if i + 1 < len(self.TIME_REMAINING_CHUNKS): seconds2, name2 = self.TIME_REMAINING_CHUNKS[i + 1] count2 = (since - (seconds * count)) // seconds2 if count2 != 0: result += ', ' + name2 % count2 return result
def shipbrief(value): return N_("SHIP_BRIEF_%i" % (value))
def buildingbrief(value): return N_("BUILDING_BRIEF_%i" % (value))
def cannonbrief(value): return N_("CANNON_BRIEF_%i" % (value))