class Notification(models.Model): project = models.ForeignKey(Project, related_name='%(class)s_notifications') objects = RelatedProjectQuerySet.as_manager() class Meta(object): abstract = True
class Domain(models.Model): """A custom domain name for a project.""" project = models.ForeignKey(Project, related_name='domains') domain = models.CharField(_('Domain'), unique=True, max_length=255, validators=[validate_domain_name]) machine = models.BooleanField(default=False, help_text=_('This Domain was auto-created')) cname = models.BooleanField( default=False, help_text=_('This Domain is a CNAME for the project')) canonical = models.BooleanField( default=False, help_text=_( 'This Domain is the primary one where the documentation is ' 'served from')) https = models.BooleanField( _('Use HTTPS'), default=False, help_text=_('Always use HTTPS for this domain')) count = models.IntegerField( default=0, help_text=_('Number of times this domain has been hit'), ) objects = RelatedProjectQuerySet.as_manager() class Meta(object): ordering = ('-canonical', '-machine', 'domain') def __str__(self): return '{domain} pointed at {project}'.format( domain=self.domain, project=self.project.name) def save(self, *args, **kwargs): # pylint: disable=arguments-differ from readthedocs.projects import tasks parsed = urlparse(self.domain) if parsed.scheme or parsed.netloc: self.domain = parsed.netloc else: self.domain = parsed.path super(Domain, self).save(*args, **kwargs) broadcast( type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk], ) def delete(self, *args, **kwargs): # pylint: disable=arguments-differ from readthedocs.projects import tasks broadcast( type='app', task=tasks.symlink_domain, args=[self.project.pk, self.pk, True], ) super(Domain, self).delete(*args, **kwargs)
class EnvironmentVariable(TimeStampedModel, models.Model): name = models.CharField( max_length=128, help_text=_('Name of the environment variable'), ) value = models.CharField( max_length=2048, help_text=_('Value of the environment variable'), ) project = models.ForeignKey( Project, on_delete=models.CASCADE, help_text=_('Project where this variable will be used'), ) objects = RelatedProjectQuerySet.as_manager() def __str__(self): return self.name def save(self, *args, **kwargs): # pylint: disable=arguments-differ self.value = quote(self.value) return super().save(*args, **kwargs)
class SearchQuery(TimeStampedModel): """Information about the search queries.""" project = models.ForeignKey( Project, related_name='search_queries', on_delete=models.CASCADE, ) version = models.ForeignKey( Version, verbose_name=_('Version'), related_name='search_queries', on_delete=models.CASCADE, ) query = models.CharField( _('Query'), max_length=4092, ) total_results = models.IntegerField( _('Total results'), default=0, ) objects = RelatedProjectQuerySet.as_manager() class Meta: verbose_name = 'Search query' verbose_name_plural = 'Search queries' def __str__(self): return f'[{self.project.slug}:{self.version.slug}]: {self.query}' @classmethod def generate_queries_count_of_one_month(cls, project_slug): """ Returns the total queries performed each day of the last 30 days (including today). Structure of returned data is compatible to make graphs. Sample returned data:: { 'labels': ['01 Jul', '02 Jul', '03 Jul'], 'int_data': [150, 200, 143] } This data shows that there were 150 searches were made on 01 July, 200 searches on 02 July and 143 searches on 03 July. """ today = timezone.now().date() last_30th_day = timezone.now().date() - timezone.timedelta(days=30) qs = cls.objects.filter( project__slug=project_slug, created__date__lte=today, created__date__gte=last_30th_day, ).order_by('-created') # dict containing the total number of queries # of each day for the past 30 days (if present in database). count_dict = dict( qs.annotate(created_date=TruncDate('created')).values( 'created_date').order_by('created_date').annotate( count=Count('id')).values_list('created_date', 'count')) count_data = [ count_dict.get(date) or 0 for date in _last_30_days_iter() ] # format the date value to a more readable form # Eg. `16 Jul` last_30_days_str = [ timezone.datetime.strftime(date, '%d %b') for date in _last_30_days_iter() ] final_data = { 'labels': last_30_days_str, 'int_data': count_data, } return final_data
class SphinxDomain(TimeStampedModel): """ Information from a project about it's Sphinx domains. This captures data about API objects that exist in that codebase. """ project = models.ForeignKey( Project, related_name='sphinx_domains', on_delete=models.CASCADE, ) version = models.ForeignKey( Version, verbose_name=_('Version'), related_name='sphinx_domains', on_delete=models.CASCADE, ) html_file = models.ForeignKey( HTMLFile, related_name='sphinx_domains', null=True, on_delete=models.CASCADE, ) commit = models.CharField(_('Commit'), max_length=255, null=True) build = models.IntegerField(_('Build id'), null=True) domain = models.CharField( _('Domain'), max_length=255, ) name = models.CharField( _('Name'), max_length=4092, ) display_name = models.CharField( _('Display Name'), max_length=4092, ) type = models.CharField( _('Type'), max_length=255, ) type_display = models.CharField( _('Type Display'), max_length=4092, null=True, ) doc_name = models.CharField( _('Doc Name'), max_length=4092, ) doc_display = models.CharField( _('Doc Display'), max_length=4092, null=True, ) anchor = models.CharField( _('Anchor'), max_length=4092, ) objects = RelatedProjectQuerySet.as_manager() def __str__(self): ret = f''' SphinxDomain [{self.project.slug}:{self.version.slug}] [{self.domain}:{self.type}] {self.name} -> {self.doc_name} '''.strip() if self.anchor: ret += f'#{self.anchor}' return ret @property def role_name(self): return f'{self.domain}:{self.type}' @property def docs_url(self): path = self.doc_name if self.anchor: path += f'#{self.anchor}' full_url = resolve( project=self.project, version_slug=self.version.slug, filename=path, ) return full_url
class SearchQuery(TimeStampedModel): """Information about the search queries.""" project = models.ForeignKey( Project, related_name='search_queries', on_delete=models.CASCADE, ) version = models.ForeignKey( Version, verbose_name=_('Version'), related_name='search_queries', on_delete=models.CASCADE, ) query = models.CharField( _('Query'), max_length=4092, ) objects = RelatedProjectQuerySet.as_manager() class Meta: verbose_name = 'Search query' verbose_name_plural = 'Search queries' def __str__(self): return f'[{self.project.slug}:{self.version.slug}]: {self.query}' @classmethod def generate_queries_count_of_one_month(cls, project_slug): """ Returns the total queries performed each day of the last 30 days (including today). Structure of returned data is compatible to make graphs. Sample returned data:: { 'labels': ['01 Jul', '02 Jul', '03 Jul'], 'int_data': [150, 200, 143] } This data shows that there were 150 searches were made on 01 July, 200 searches on 02 July and 143 searches on 03 July. """ today = timezone.now().date() last_30th_day = timezone.now().date() - timezone.timedelta(days=30) # this includes the current day also last_31_days_iter = [ last_30th_day + timezone.timedelta(days=n) for n in range(31) ] qs = cls.objects.filter( project__slug=project_slug, created__date__lte=today, created__date__gte=last_30th_day, ).order_by('-created') # dict containing the total number of queries # of each day for the past 30 days (if present in database). count_dict = dict( qs.annotate(created_date=TruncDate('created')).values( 'created_date').order_by('created_date').annotate( count=Count('id')).values_list('created_date', 'count')) count_data = [count_dict.get(date) or 0 for date in last_31_days_iter] # format the date value to a more readable form # Eg. `16 Jul` last_31_days_str = [ timezone.datetime.strftime(date, '%d %b') for date in last_31_days_iter ] final_data = { 'labels': last_31_days_str, 'int_data': count_data, } return final_data @classmethod def generate_distribution_of_top_queries(cls, project_slug, n): """ Returns top `n` most searched queries with their count. Structure of returned data is compatible to make graphs. Sample returned data:: { 'labels': ['read the docs', 'documentation', 'sphinx'], 'int_data': [150, 200, 143] } This data shows that `read the docs` was searched 150 times, `documentation` was searched 200 times and `sphinx` was searched 143 times. """ qs = cls.objects.filter(project__slug=project_slug) # total searches ever made total_count = len(qs) # search queries with their count # Eg. [('read the docs', 150), ('documentation', 200), ('sphinx', 143')] count_of_each_query = (qs.values('query').annotate( count=Count('id')).order_by('-count').values_list( 'query', 'count')) # total number of searches made for top `n` queries count_of_top_n = sum([value[1] for value in count_of_each_query][:n]) # total number of remaining searches count_of_other = total_count - count_of_top_n final_data = { 'labels': [value[0] for value in count_of_each_query][:n], 'int_data': [value[1] for value in count_of_each_query][:n], } if count_of_other: final_data['labels'].append('Other queries') final_data['int_data'].append(count_of_other) return final_data