Source code for quade.models
from __future__ import absolute_import, division, print_function, unicode_literals
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models as m
from django.utils.encoding import python_2_unicode_compatible
from django_fsm.db.fields import FSMIntegerField, transition
from django_light_enums import enum
from jsonfield import JSONField
from .managers import manager, ConfigurationError
from .receivers import connect_qa_object_receiver
class ScenarioManager(m.QuerySet):
def active(self):
return self.filter(status=Scenario.Status.ACTIVE)
[docs]@python_2_unicode_compatible
class Scenario(m.Model):
"""
A specific scenario that QA tests will be run against.
The heart of a ``Scenario`` is the `config` attribute. `config` is a list of several fixtures,
possibly with arguments, which are executed sequentially to create and modify objects in the
database.
"""
class Meta:
app_label = 'quade'
ordering = ['description']
class Status(enum.Enum):
INACTIVE = 0
ACTIVE = 10
objects = ScenarioManager.as_manager()
REGISTRY = {}
slug = m.SlugField(unique=True)
status = enum.EnumField(Status, default=Status.ACTIVE)
config = JSONField()
description = m.TextField()
created_on = m.DateTimeField(auto_now_add=True)
updated_on = m.DateTimeField(auto_now=True)
def __str__(self):
return '#{}: {}'.format(self.id, self.description)
def activate(self):
self.status = self.Status.ACTIVE
self.save()
def deactivate(self):
self.status = self.Status.INACTIVE
self.save()
def delete(self):
self.deactivate()
def hard_delete(self, *args, **kwargs):
super(Scenario, self).delete(*args, **kwargs)
def save(self, *args, **kwargs):
try:
manager.validate(self.config)
except ConfigurationError as exc:
raise ValidationError("config {} contains unregistered function(s): {}".format(
self.config, ','.join(exc.unregistered_functions)
))
super(Scenario, self).save(*args, **kwargs)
[docs]class Record(m.Model):
"""
A record of setting up, and possibly executing, a particular :class:`Scenario`.
"""
class Meta:
app_label = 'quade'
class Status(enum.Enum):
FAILED = -10
NOT_READY = -1
READY = 1 # Django-FSM doesn't work well with 0, because it evaluates as falsey.
IN_PROGRESS = 10
DONE = 20
scenario = m.ForeignKey(Scenario, on_delete=m.PROTECT)
instructions = m.TextField(
blank=True,
null=True,
help_text="Information needed to run this particular scenario, such as usernames and login"
" information."
)
status = FSMIntegerField(choices=Status.choices, default=Status.NOT_READY)
created_by = m.ForeignKey(settings.AUTH_USER_MODEL, related_name='+', on_delete=m.PROTECT)
created_on = m.DateTimeField(auto_now_add=True)
updated_on = m.DateTimeField(auto_now=True)
[docs] def execute_test(self):
"""
Public method for attempting to execute a test scenario. Exceptions put the record in the
FAILED state and re-raise.
"""
try:
self._execute()
except Exception as ex:
self._fail(exception=ex)
raise
@transition(status, source=Status.NOT_READY, target=Status.READY, save=True)
def _execute(self):
with connect_qa_object_receiver(self):
instructions = manager.execute(self.scenario.config)
self.instructions = instructions
@transition(status, source=Status.NOT_READY, target=Status.FAILED, save=True)
def _fail(self, exception):
self.instructions = repr(exception)
[docs]@python_2_unicode_compatible
class RecordedObject(m.Model):
"""
A joiner table for creating a generic many-to-many relation between :class:`Records <Record>`
and any other objects.
"""
class Meta:
app_label = 'quade'
content_type = m.ForeignKey(ContentType, on_delete=m.CASCADE)
object_id = m.PositiveIntegerField()
object = GenericForeignKey('content_type', 'object_id')
record = m.ForeignKey(Record, related_name='recorded_objects', on_delete=m.CASCADE)
def __str__(self):
return "RecordedObject #{}: {}".format(self.pk, self.object)