layerindex-web/rrs/models.py
Paul Eggleton fdc8209b29 rrs: make changes to support Django 2.2
Removed obsolete references to django.core.urlresolvers. Added the
newly required on_delete parameters to foreign key relationships in
models.py and in all migrations.

(Amber Elliot's 2d526f9b0d served as a
reference for these changes).

Signed-off-by: Paul Eggleton <paul.eggleton@linux.microsoft.com>
2021-10-20 10:02:58 +13:00

567 lines
20 KiB
Python

# rrs-web - model definitions
#
# Copyright (C) 2014 Intel Corporation
#
# Licensed under the MIT license, see COPYING.MIT for details
#
# SPDX-License-Identifier: MIT
import sys
import os
import re
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '../')))
from datetime import date, datetime
from django.db import models
from django.contrib.auth.models import User
from layerindex.models import Recipe, LayerBranch, PythonEnvironment
from django.core.exceptions import ObjectDoesNotExist
class MaintenancePlan(models.Model):
MAINTENANCEPLAN_MAINTAINER_STYLE = (
('I', 'Per-recipe - maintainers.inc'),
('L', 'Layer-wide'),
)
name = models.CharField(max_length=50, unique=True)
description = models.TextField(blank=True)
updates_enabled = models.BooleanField('Enable updates', default=True, help_text='Enable automatically updating metadata for this plan via the update scripts')
email_enabled = models.BooleanField('Enable emails', default=False, help_text='Enable automatically sending report emails for this plan')
email_subject = models.CharField(max_length=255, blank=True, default='[Recipe reporting system] Upgradable recipe name list', help_text='Subject line of automated emails')
email_from = models.CharField(max_length=255, blank=True, help_text='Sender for automated emails')
email_to = models.CharField(max_length=255, blank=True, help_text='Recipient for automated emails (separate multiple addresses with ;)')
admin = models.ForeignKey(User, blank=True, null=True, help_text='Plan administrator', on_delete=models.SET_NULL)
maintainer_style = models.CharField(max_length=1, choices=MAINTENANCEPLAN_MAINTAINER_STYLE, default='L', help_text='Maintainer tracking style for the layers within this plan')
def get_default_release(self):
return self.release_set.filter(milestone__isnull=False).order_by('-start_date').first()
def per_recipe_maintainers(self):
return self.maintainer_style != 'L'
def __str__(self):
return '%s' % (self.name)
class MaintenancePlanLayerBranch(models.Model):
plan = models.ForeignKey(MaintenancePlan, on_delete=models.CASCADE)
layerbranch = models.ForeignKey(LayerBranch, on_delete=models.CASCADE)
python3_switch_date = models.DateTimeField('Commit date to switch to Python 3', default=datetime(2016, 6, 2))
python2_environment = models.ForeignKey(PythonEnvironment, related_name='maintplan_layerbranch_python2_set', blank=True, null=True, help_text='Environment to use for Python 2 commits', on_delete=models.SET_NULL)
python3_environment = models.ForeignKey(PythonEnvironment, related_name='maintplan_layerbranch_python3_set', blank=True, null=True, help_text='Environment to use for Python 3 commits', on_delete=models.SET_NULL)
upgrade_date = models.DateTimeField('Recipe upgrade date', blank=True, null=True)
upgrade_rev = models.CharField('Recipe upgrade revision ', max_length=80, blank=True)
class Meta:
verbose_name_plural = "Maintenance plan layer branches"
class Release(models.Model):
plan = models.ForeignKey(MaintenancePlan, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
start_date = models.DateField(db_index=True)
end_date = models.DateField(db_index=True)
class Meta:
unique_together = ('plan', 'name',)
def get_default_milestone(self):
return self.milestone_set.order_by('-start_date').first()
@staticmethod
def get_by_date(maintplan, date):
release_qry = Release.objects.filter(plan=maintplan,
start_date__lte = date,
end_date__gte = date).order_by('-end_date')
if release_qry:
return release_qry[0]
else:
return None
@staticmethod
def get_current(maintplan):
current = date.today()
current_release = Release.get_by_date(maintplan, current)
if current_release:
return current_release
else:
plan_releases = Release.objects.filter(plan=maintplan).order_by('-end_date')
if plan_releases:
return plan_releases[0]
return None
def __str__(self):
return '%s - %s' % (self.plan.name, self.name)
class Milestone(models.Model):
release = models.ForeignKey(Release, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
start_date = models.DateField(db_index=True)
end_date = models.DateField(db_index=True)
class Meta:
unique_together = ('release', 'name',)
""" Get milestones, filtering don't exist yet and ordering """
@staticmethod
def get_by_release_name(maintplan, release_name):
milestones = []
today = date.today()
try:
mall = Milestone.objects.get(release__plan=maintplan, release__name=release_name, name='All')
except ObjectDoesNotExist:
mall = None
if mall:
milestones.append(mall)
mqry = Milestone.objects.filter(release__plan=maintplan, release__name=release_name).order_by('-end_date')
for m in mqry:
if m.name == 'All':
continue
if m.start_date > today:
continue
milestones.append(m)
return milestones
""" Get milestone by release and date """
@staticmethod
def get_by_release_and_date(release, date):
milestone_set = Milestone.objects.filter(release = release,
start_date__lte = date, end_date__gte = date). \
exclude(name = 'All').order_by('-end_date')
if milestone_set:
return milestone_set[0]
else:
return None
""" Get current milestone """
@staticmethod
def get_current(release):
current_milestone = None
current_date = date.today()
mqry = Milestone.objects.filter(release = release, start_date__lte = current_date,
end_date__gte = current_date).exclude(name = 'All').order_by('-end_date')
if mqry:
current_milestone = mqry[0]
else:
current_milestone = Milestone.objects.filter(release = release). \
order_by('-end_date')[0]
return current_milestone
""" Get milestone intervals by release """
@staticmethod
def get_milestone_intervals(release):
milestones = Milestone.objects.filter(release = release)
milestone_dir = {}
for m in milestones:
if "All" in m.name:
continue
milestone_dir[m.name] = {}
milestone_dir[m.name]['start_date'] = m.start_date
milestone_dir[m.name]['end_date'] = m.end_date
return milestone_dir
""" Get week intervals from start and end of Milestone """
def get_week_intervals(self):
from datetime import timedelta
weeks = {}
week_delta = timedelta(weeks=1)
week_no = 1
current_date = self.start_date
while True:
if current_date >= self.end_date:
break;
weeks[week_no] = {}
weeks[week_no]['start_date'] = current_date
weeks[week_no]['end_date'] = current_date + week_delta
current_date += week_delta
week_no += 1
return weeks
def __str__(self):
return '%s: %s %s' % (self.release.plan.name, self.release.name, self.name)
class RecipeSymbol(models.Model):
layerbranch = models.ForeignKey(LayerBranch, on_delete=models.CASCADE)
pn = models.CharField(max_length=100, blank=True)
summary = models.CharField(max_length=200, blank=True)
@staticmethod
def symbol(pn, layerbranch, summary=None):
rsym, created = RecipeSymbol.objects.get_or_create(pn=pn, layerbranch=layerbranch)
if created:
if summary:
rsym.summary = summary
else:
recipe = Recipe.objects.filter(pn=pn, layerbranch=layerbranch).first()
if recipe:
rsym.summary = recipe.summary
rsym.save()
return rsym
def __str__(self):
return "%s: %s" % (str(self.layerbranch), self.pn)
class Maintainer(models.Model):
name = models.CharField(max_length=255, unique=True)
email = models.CharField(max_length=255, blank=True)
"""
Create maintainer if no exist else update email.
Return Maintainer.
"""
@staticmethod
def create_or_update(name, email):
try:
m = Maintainer.objects.get(name = name)
m.email = email
except Maintainer.DoesNotExist:
m = Maintainer()
m.name = name
m.email = email
m.save()
return m
class Meta:
ordering = ["name"]
def __str__(self):
return "%s <%s>" % (self.name, self.email)
class RecipeMaintainerHistory(models.Model):
title = models.CharField(max_length=255, blank=True)
date = models.DateTimeField(db_index=True)
author = models.ForeignKey(Maintainer, on_delete=models.CASCADE)
sha1 = models.CharField(max_length=64)
layerbranch = models.ForeignKey(LayerBranch, on_delete=models.CASCADE)
class Meta:
unique_together = ('layerbranch', 'sha1',)
@staticmethod
def get_last(layerbranch):
rmh_qry = RecipeMaintainerHistory.objects.filter(layerbranch=layerbranch).order_by('-date')
if rmh_qry:
return rmh_qry[0]
else:
return None
@staticmethod
def get_by_end_date(layerbranch, end_date):
rmh_qry = RecipeMaintainerHistory.objects.filter(
layerbranch=layerbranch,
date__lte = end_date).order_by('-date')
if rmh_qry:
return rmh_qry[0]
rmh_qry = RecipeMaintainerHistory.objects.filter(
layerbranch=layerbranch
).order_by('date')
if rmh_qry:
return rmh_qry[0]
else:
return None
def __str__(self):
return "%s: %s, %s" % (self.date, self.author.name, self.sha1[:10])
class RecipeMaintainer(models.Model):
recipesymbol = models.ForeignKey(RecipeSymbol, on_delete=models.CASCADE)
maintainer = models.ForeignKey(Maintainer, on_delete=models.CASCADE)
history = models.ForeignKey(RecipeMaintainerHistory, on_delete=models.CASCADE)
@staticmethod
def get_maintainer_by_recipe_and_history(recipe, history):
qry = RecipeMaintainer.objects.filter(recipesymbol__pn=recipe.pn,
history=history)
if qry:
return qry[0].maintainer
else:
return None
def __str__(self):
return "%s: %s <%s>" % (self.recipesymbol.pn, self.maintainer.name,
self.maintainer.email)
class RecipeUpstreamHistory(models.Model):
layerbranch = models.ForeignKey(LayerBranch, on_delete=models.CASCADE)
start_date = models.DateTimeField(db_index=True)
end_date = models.DateTimeField(db_index=True)
@staticmethod
def get_last_by_date_range(layerbranch, start, end):
history = RecipeUpstreamHistory.objects.filter(layerbranch=layerbranch,
start_date__gte = start,
start_date__lte = end).order_by('-start_date')
if history:
return history[0]
else:
return None
@staticmethod
def get_first_by_date_range(layerbranch, start, end):
history = RecipeUpstreamHistory.objects.filter(layerbranch=layerbranch,
start_date__gte = start,
start_date__lte = end).order_by('start_date')
if history:
return history[0]
else:
return None
@staticmethod
def get_last(layerbranch):
history = RecipeUpstreamHistory.objects.filter(layerbranch=layerbranch).order_by('-start_date')
if history:
return history[0]
else:
return None
def __str__(self):
return '%s: %s' % (self.id, self.start_date)
class RecipeUpstream(models.Model):
RECIPE_UPSTREAM_STATUS_CHOICES = (
('A', 'All'),
('N', 'Not updated'),
('C', 'Can\'t be updated'),
('Y', 'Up-to-date'),
('D', 'Downgrade'),
('U', 'Unknown'),
)
RECIPE_UPSTREAM_STATUS_CHOICES_DICT = dict(RECIPE_UPSTREAM_STATUS_CHOICES)
RECIPE_UPSTREAM_TYPE_CHOICES = (
('A', 'Automatic'),
('M', 'Manual'),
)
RECIPE_UPSTREAM_TYPE_CHOICES_DICT = dict(RECIPE_UPSTREAM_TYPE_CHOICES)
recipesymbol = models.ForeignKey(RecipeSymbol, on_delete=models.CASCADE)
history = models.ForeignKey(RecipeUpstreamHistory, on_delete=models.CASCADE)
version = models.CharField(max_length=100, blank=True)
type = models.CharField(max_length=1, choices=RECIPE_UPSTREAM_TYPE_CHOICES, blank=True, db_index=True)
status = models.CharField(max_length=1, choices=RECIPE_UPSTREAM_STATUS_CHOICES, blank=True, db_index=True)
no_update_reason = models.CharField(max_length=255, blank=True, db_index=True)
date = models.DateTimeField(db_index=True)
@staticmethod
def get_all_recipes(history):
qry = RecipeUpstream.objects.filter(history = history)
return qry
@staticmethod
def get_recipes_not_updated(history):
qry = RecipeUpstream.objects.filter(history = history, status = 'N',
no_update_reason = '').order_by('pn')
return qry
@staticmethod
def get_recipes_cant_be_updated(history):
qry = RecipeUpstream.objects.filter(history = history, status = 'N') \
.exclude(no_update_reason = '').order_by('pn')
return qry
@staticmethod
def get_recipes_up_to_date(history):
qry = RecipeUpstream.objects.filter(history = history, status = 'Y' \
).order_by('pn')
return qry
@staticmethod
def get_recipes_unknown(history):
qry = RecipeUpstream.objects.filter(history = history,
status__in = ['U', 'D']).order_by('pn')
return qry
@staticmethod
def get_by_recipe_and_history(recipesymbol, history):
ru = RecipeUpstream.objects.filter(recipesymbol=recipesymbol, history=history)
return ru[0] if ru else None
def needs_upgrade(self):
if self.status == 'N':
return True
else:
return False
def __str__(self):
return '%s: (%s, %s, %s)' % (self.recipesymbol.pn, self.status,
self.version, self.date)
class RecipeDistro(models.Model):
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
distro = models.CharField(max_length=100, blank=True)
alias = models.CharField(max_length=100, blank=True)
def __str__(self):
return '%s: %s' % (self.recipe.pn, self.distro)
@staticmethod
def get_distros_by_recipe(recipe):
recipe_distros = []
query = RecipeDistro.objects.filter(recipe = recipe).order_by('distro')
for q in query:
recipe_distros.append(q.distro)
return recipe_distros
class RecipeUpgradeGroup(models.Model):
recipesymbol = models.ForeignKey(RecipeSymbol, on_delete=models.CASCADE)
title = models.CharField(max_length=100, help_text='Group title')
def __str__(self):
return '%s: %s' % (self.recipesymbol, self.title)
class RecipeUpgradeGroupRule(models.Model):
layerbranch = models.ForeignKey(LayerBranch, on_delete=models.CASCADE)
pn = models.CharField(max_length=100, blank=True, help_text='Regular expression to match recipe to apply to')
version = models.CharField(max_length=100, blank=True, help_text='Regular expression to split version component on')
license = models.CharField(max_length=100, blank=True, help_text='Regular expression to split license on')
priority = models.IntegerField(blank=True, null=True, help_text='Order to apply rule in (higher first)')
class Meta:
ordering = ["-priority"]
@staticmethod
def group_for_params(recipesymbol, version, license):
for rule in RecipeUpgradeGroupRule.objects.filter(layerbranch=recipesymbol.layerbranch).order_by('-priority'):
pnmatch = None
if rule.pn:
pnmatch = re.match(rule.pn, recipesymbol.pn)
if (not rule.pn) or pnmatch:
if rule.version:
res = re.match(rule.version, version)
if res:
if res.groups():
match = res.groups()[0]
else:
match = res.string[res.start(0):res.end(0)]
group, _ = RecipeUpgradeGroup.objects.get_or_create(recipesymbol=recipesymbol, title=match)
group.save()
return group
elif rule.license:
res = re.match(rule.license, license)
if res:
if res.groups():
match = res.groups()[0]
else:
match = res.string[res.start(0):res.end(0)]
group, _ = RecipeUpgradeGroup.objects.get_or_create(recipesymbol=recipesymbol, title=match)
group.save()
return group
elif pnmatch:
# Allow for dummy rules
return None
return None
def __str__(self):
return '%s: %s' % (self.layerbranch, self.pn)
class RecipeUpgrade(models.Model):
UPGRADE_TYPE_CHOICES = (
('U', 'Upgrade'),
('D', 'Downgrade'),
('N', 'Delete'),
('R', 'Delete (final)'),
('M', 'Move'),
)
recipesymbol = models.ForeignKey(RecipeSymbol, on_delete=models.CASCADE)
maintainer = models.ForeignKey(Maintainer, blank=True, null=True, on_delete=models.SET_NULL)
sha1 = models.CharField(max_length=40, blank=True)
title = models.CharField(max_length=1024, blank=True)
version = models.CharField(max_length=100, blank=True)
srcrev = models.CharField(max_length=64, blank=True)
license = models.CharField(max_length=100, blank=True)
author_date = models.DateTimeField(db_index=True)
commit_date = models.DateTimeField(db_index=True)
upgrade_type = models.CharField(max_length=1, choices=UPGRADE_TYPE_CHOICES, default='U', db_index=True)
filepath = models.CharField(max_length=512, blank=True)
orig_filepath = models.CharField(max_length=512, blank=True)
group = models.ForeignKey(RecipeUpgradeGroup, blank=True, null=True, on_delete=models.SET_NULL)
prev_version = models.CharField(max_length=100, blank=True)
@staticmethod
def get_by_recipe_and_date(recipe, end_date):
ru = RecipeUpgrade.objects.filter(recipesymbol__pn=recipe.pn,
recipesymbol__layerbranch=recipe.layerbranch,
commit_date__lte = end_date)
return ru[len(ru) - 1] if ru else None
def short_sha1(self):
return self.sha1[0:6]
def commit_url(self):
return self.recipesymbol.layerbranch.commit_url(self.sha1)
def regroup(self):
group = RecipeUpgradeGroupRule.group_for_params(self.recipesymbol, self.version, self.license)
if group != self.group:
self.group = group
return True
else:
return False
def __str__(self):
if self.upgrade_type == 'R':
return '%s: deleted [final] (%s)' % (self.recipesymbol.pn,
self.commit_date)
elif self.upgrade_type == 'N':
return '%s: deleted (%s)' % (self.recipesymbol.pn,
self.commit_date)
elif self.upgrade_type == 'M':
return '%s: moved (%s)' % (self.recipesymbol.pn,
self.commit_date)
else:
return '%s: (%s, %s)' % (self.recipesymbol.pn, self.version,
self.commit_date)
class RecipeMaintenanceLink(models.Model):
pn_match = models.CharField(max_length=100, help_text='Expression to match against pn of recipes that should be linked (glob expression)')
pn_target = models.CharField(max_length=100, help_text='Name of recipe to link to')
@staticmethod
def link_maintainer(pn, rmh):
import fnmatch
for rml in RecipeMaintenanceLink.objects.all():
if fnmatch.fnmatch(pn, rml.pn_match):
recipe_link_objs = rmh.layerbranch.recipe_set.filter(pn=rml.pn_target)
if recipe_link_objs:
lrm = RecipeMaintainer.objects.filter(recipesymbol__pn=recipe_link_objs[0].pn, history=rmh)
if lrm:
return lrm[0]
return None
def __str__(self):
return '%s -> %s' % (self.pn_match, self.pn_target)