layerindex-web/rrs/models.py
Paul Eggleton 32617fc366 rrs: fix unique constraint on RecipeMaintainerHistory sha1 field
Although it's unlikely to be an issue, technically we shouldn't be
insisting the sha1 field be unique globally, just within each
layerbranch, so adjust the constraints.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
2018-05-04 23:57:53 +12:00

454 lines
15 KiB
Python

# rrs-web - model definitions
#
# Copyright (C) 2014 Intel Corporation
#
# Licensed under the MIT license, see COPYING.MIT for details
import sys
import os
import os.path
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')
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.last()
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)
layerbranch = models.ForeignKey(LayerBranch)
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')
python3_environment = models.ForeignKey(PythonEnvironment, related_name='maintplan_layerbranch_python3_set', blank=True, null=True, help_text='Environment to use for Python 3 commits')
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)
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.last()
@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)
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 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)
sha1 = models.CharField(max_length=64)
layerbranch = models.ForeignKey(LayerBranch)
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):
recipe = models.ForeignKey(Recipe)
maintainer = models.ForeignKey(Maintainer)
history = models.ForeignKey(RecipeMaintainerHistory)
@staticmethod
def get_maintainer_by_recipe_and_history(recipe, history):
qry = RecipeMaintainer.objects.filter(recipe = recipe,
history = history)
if qry:
return qry[0].maintainer
else:
return None
def __str__(self):
return "%s: %s <%s>" % (self.recipe.pn, self.maintainer.name,
self.maintainer.email)
class RecipeUpstreamHistory(models.Model):
layerbranch = models.ForeignKey(LayerBranch)
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)
recipe = models.ForeignKey(Recipe)
history = models.ForeignKey(RecipeUpstreamHistory)
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(recipe, history):
ru = RecipeUpstream.objects.filter(recipe = recipe, 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.recipe.pn, self.status,
self.version, self.date)
class RecipeDistro(models.Model):
recipe = models.ForeignKey(Recipe)
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 RecipeUpgrade(models.Model):
recipe = models.ForeignKey(Recipe)
maintainer = models.ForeignKey(Maintainer, blank=True)
sha1 = models.CharField(max_length=40, blank=True)
title = models.CharField(max_length=1024, blank=True)
version = models.CharField(max_length=100, blank=True)
author_date = models.DateTimeField(db_index=True)
commit_date = models.DateTimeField(db_index=True)
@staticmethod
def get_by_recipe_and_date(recipe, end_date):
ru = RecipeUpgrade.objects.filter(recipe = recipe,
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.recipe.layerbranch.commit_url(self.sha1)
def __str__(self):
return '%s: (%s, %s)' % (self.recipe.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(recipe=recipe_link_objs[0], history=rmh)
if lrm:
return lrm[0]
return None
def __str__(self):
return '%s -> %s' % (self.pn_match, self.pn_target)