<commit_before># -*- coding: utf8 -*-
import sys
from datetime import datetime

from rest_framework.test import APITestCase, force_authenticate, APIRequestFactory

from django.utils import unittest, timezone
from django.test.utils import override_settings

from basis.compat import get_user_model
from .models import Person
from .views import BasisModelViewSet

PY3 = sys.version_info[0] == 3

if PY3:
    from unittest import mock
else:
    import mock

class TestTimeStampModel(unittest.TestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username="test1")[0]
        self.user2 = get_user_model().objects.get_or_create(username="test2")[0]

    def tearDown(self):
        get_user_model().objects.all().delete()
        Person.all_objects.all().delete()

    def test_datetimes(self):
        person = Person.objects.create(current_user=self.user1)

        self.assertAlmostEqual(int(person.created_at.strftime('%Y%m%d%H%M%S')),
                               int(datetime.now().strftime('%Y%m%d%H%M%S')))
        person.save()
        self.assertNotEqual(person.created_at, person.updated_at)
        self.assertAlmostEqual(int(person.updated_at.strftime('%Y%m%d%H%M%S')),
                               int(datetime.now().strftime('%Y%m%d%H%M%S')))

    @override_settings(USE_TZ=True)
    def test_datetimes_with_timezone(self):
        person = Person.objects.create(current_user=self.user1)
        self.assertEqual(person.created_at.tzinfo, timezone.utc)
        self.assertEqual(person.updated_at.tzinfo, timezone.utc)

        self.assertAlmostEqual(int(person.created_at.strftime('%Y%m%d%H%M%S')),
                               int(timezone.now().strftime('%Y%m%d%H%M%S')))
        person.save()
        self.assertNotEqual(person.created_at, person.updated_at)
        self.assertAlmostEqual(int(person.updated_at.strftime('%Y%m%d%H%M%S')),
                               int(timezone.now().strftime('%Y%m%d%H%M%S')))

class TestPersistentModel(unittest.TestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username='test1')[0]
        self.user2 = get_user_model().objects.get_or_create(username='test2')[0]

    def tearDown(self):
        get_user_model().objects.all().delete()
        Person.all_objects.all().delete()

    def test_delete_person(self):
        person = Person.objects.create(current_user=self.user1)

        self.assertEqual(Person.objects.all().count(), 1)
        person.delete()
        self.assertEqual(Person.objects.all().count(), 0)
        self.assertEqual(Person.all_objects.all().count(), 1)

        person.restore()
        self.assertEqual(Person.objects.all().count(), 1)
        Person.all_objects.all()[0].delete()
        self.assertEqual(Person.objects.all().count(), 0)
        self.assertEqual(Person.all_objects.all().count(), 1)

    def test_force_delete(self):
        person = Person.objects.create(current_user=self.user1)
        person.delete(force=True)
        self.assertEqual(Person.all_objects.count(), 0)

    @mock.patch('django.db.models.base.Model.delete')
    def test_delete_with_using(self, mock_delete):
        person = Person.objects.create(current_user=self.user1)
        person.delete(force=True, using='other_db')
        mock_delete.assert_called_with('other_db')

class TestBasisModel(unittest.TestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username="test1")[0]
        self.user2 = get_user_model().objects.get_or_create(username="test2")[0]

    def tearDown(self):
        get_user_model().objects.all().delete()
        Person.all_objects.all().delete()

    def test_save_person_without_user(self):
        person = Person()
        person.save()

        self.assertEqual(person.created_by, None)
        self.assertEqual(person.updated_by, None)

    def test_save_person_with_user(self):
        person = Person(name='john doe')
        person.save(current_user=self.user1)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user1)

        p = Person.objects.get(name='john doe')
        self.assertEqual(p.created_by, self.user1)
        self.assertEqual(p.updated_by, self.user1)

    def test_create_person_without_user(self):
        person = Person.objects.create(name='john doe')

        self.assertEqual(person.created_by, None)
        self.assertEqual(person.updated_by, None)

        p = Person.objects.get(name='john doe')
        self.assertEqual(p.created_by, None)
        self.assertEqual(p.updated_by, None)

    def test_create_person_with_user(self):
        person = Person.objects.create(name='john doe', current_user=self.user1)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user1)

        p = Person.objects.get(name='john doe')
        self.assertEqual(p.created_by, self.user1)
        self.assertEqual(p.updated_by, self.user1)

    def test_update_person_with_user(self):
        person = Person.objects.create(name='john doe', current_user=self.user1)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user1)

        p = Person.objects.get(name='john doe')
        self.assertEqual(p.created_by, self.user1)
        self.assertEqual(p.updated_by, self.user1)

        person.save(current_user=self.user2)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user2)

        p = Person.objects.get(name='john doe')
        self.assertEqual(p.created_by, self.user1)
        self.assertEqual(p.updated_by, self.user2)

class TestBasisSerializer(APITestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username="test1")[0]
        self.user2 = get_user_model().objects.get_or_create(username="test2")[0]

        self.factory = APIRequestFactory()
        self.request_post = self.factory.post('/', {'name': 'username 123'})
        self.view_post = BasisModelViewSet.as_view({'post': 'create'})

        self.request_put = self.factory.put('/', {'name': 'username 321'})
        self.view_put = BasisModelViewSet.as_view({'put': 'update'})

    def test_created_by(self):
        force_authenticate(self.request_post, user=self.user1)
        response = self.view_post(self.request_post)

        self.assertEqual(response.data['created_by'], self.user1.pk)
        self.assertEqual(response.data['updated_by'], self.user1.pk)

    def test_updated_by(self):
        user = Person(name="username 123")
        user.save(current_user=self.user1)
        id = user.pk

        force_authenticate(self.request_put, user=self.user2)
        response = self.view_put(self.request_put, pk=id)

        self.assertNotEqual(response.data['created_by'], self.user2.pk)
        self.assertEqual(response.data['updated_by'], self.user2.pk)
<commit_msg>changed variable p to person_from_db<commit_after># -*- coding: utf8 -*-
import sys
from datetime import datetime

from rest_framework.test import APITestCase, force_authenticate, APIRequestFactory

from django.utils import unittest, timezone
from django.test.utils import override_settings

from basis.compat import get_user_model
from .models import Person
from .views import BasisModelViewSet

PY3 = sys.version_info[0] == 3

if PY3:
    from unittest import mock
else:
    import mock

class TestTimeStampModel(unittest.TestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username="test1")[0]
        self.user2 = get_user_model().objects.get_or_create(username="test2")[0]

    def tearDown(self):
        get_user_model().objects.all().delete()
        Person.all_objects.all().delete()

    def test_datetimes(self):
        person = Person.objects.create(current_user=self.user1)

        self.assertAlmostEqual(int(person.created_at.strftime('%Y%m%d%H%M%S')),
                               int(datetime.now().strftime('%Y%m%d%H%M%S')))
        person.save()
        self.assertNotEqual(person.created_at, person.updated_at)
        self.assertAlmostEqual(int(person.updated_at.strftime('%Y%m%d%H%M%S')),
                               int(datetime.now().strftime('%Y%m%d%H%M%S')))

    @override_settings(USE_TZ=True)
    def test_datetimes_with_timezone(self):
        person = Person.objects.create(current_user=self.user1)
        self.assertEqual(person.created_at.tzinfo, timezone.utc)
        self.assertEqual(person.updated_at.tzinfo, timezone.utc)

        self.assertAlmostEqual(int(person.created_at.strftime('%Y%m%d%H%M%S')),
                               int(timezone.now().strftime('%Y%m%d%H%M%S')))
        person.save()
        self.assertNotEqual(person.created_at, person.updated_at)
        self.assertAlmostEqual(int(person.updated_at.strftime('%Y%m%d%H%M%S')),
                               int(timezone.now().strftime('%Y%m%d%H%M%S')))

class TestPersistentModel(unittest.TestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username='test1')[0]
        self.user2 = get_user_model().objects.get_or_create(username='test2')[0]

    def tearDown(self):
        get_user_model().objects.all().delete()
        Person.all_objects.all().delete()

    def test_delete_person(self):
        person = Person.objects.create(current_user=self.user1)

        self.assertEqual(Person.objects.all().count(), 1)
        person.delete()
        self.assertEqual(Person.objects.all().count(), 0)
        self.assertEqual(Person.all_objects.all().count(), 1)

        person.restore()
        self.assertEqual(Person.objects.all().count(), 1)
        Person.all_objects.all()[0].delete()
        self.assertEqual(Person.objects.all().count(), 0)
        self.assertEqual(Person.all_objects.all().count(), 1)

    def test_force_delete(self):
        person = Person.objects.create(current_user=self.user1)
        person.delete(force=True)
        self.assertEqual(Person.all_objects.count(), 0)

    @mock.patch('django.db.models.base.Model.delete')
    def test_delete_with_using(self, mock_delete):
        person = Person.objects.create(current_user=self.user1)
        person.delete(force=True, using='other_db')
        mock_delete.assert_called_with('other_db')

class TestBasisModel(unittest.TestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username="test1")[0]
        self.user2 = get_user_model().objects.get_or_create(username="test2")[0]

    def tearDown(self):
        get_user_model().objects.all().delete()
        Person.all_objects.all().delete()

    def test_save_person_without_user(self):
        person = Person()
        person.save()

        self.assertEqual(person.created_by, None)
        self.assertEqual(person.updated_by, None)

    def test_save_person_with_user(self):
        person = Person(name='john doe')
        person.save(current_user=self.user1)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user1)

        person_from_db = Person.objects.get(name='john doe')
        self.assertEqual(person_from_db.created_by, self.user1)
        self.assertEqual(person_from_db.updated_by, self.user1)

    def test_create_person_without_user(self):
        person = Person.objects.create(name='john doe')

        self.assertEqual(person.created_by, None)
        self.assertEqual(person.updated_by, None)

        person_from_db = Person.objects.get(name='john doe')
        self.assertEqual(person_from_db.created_by, None)
        self.assertEqual(person_from_db.updated_by, None)

    def test_create_person_with_user(self):
        person = Person.objects.create(name='john doe', current_user=self.user1)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user1)

        person_from_db = Person.objects.get(name='john doe')
        self.assertEqual(person_from_db.created_by, self.user1)
        self.assertEqual(person_from_db.updated_by, self.user1)

    def test_update_person_with_user(self):
        person = Person.objects.create(name='john doe', current_user=self.user1)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user1)

        person_from_db = Person.objects.get(name='john doe')
        self.assertEqual(person_from_db.created_by, self.user1)
        self.assertEqual(person_from_db.updated_by, self.user1)

        person.save(current_user=self.user2)

        self.assertEqual(person.created_by, self.user1)
        self.assertEqual(person.updated_by, self.user2)

        person_from_db = Person.objects.get(name='john doe')
        self.assertEqual(person_from_db.created_by, self.user1)
        self.assertEqual(person_from_db.updated_by, self.user2)

class TestBasisSerializer(APITestCase):

    def setUp(self):
        self.user1 = get_user_model().objects.get_or_create(username="test1")[0]
        self.user2 = get_user_model().objects.get_or_create(username="test2")[0]

        self.factory = APIRequestFactory()
        self.request_post = self.factory.post('/', {'name': 'username 123'})
        self.view_post = BasisModelViewSet.as_view({'post': 'create'})

        self.request_put = self.factory.put('/', {'name': 'username 321'})
        self.view_put = BasisModelViewSet.as_view({'put': 'update'})

    def test_created_by(self):
        force_authenticate(self.request_post, user=self.user1)
        response = self.view_post(self.request_post)

        self.assertEqual(response.data['created_by'], self.user1.pk)
        self.assertEqual(response.data['updated_by'], self.user1.pk)

    def test_updated_by(self):
        user = Person(name="username 123")
        user.save(current_user=self.user1)
        id = user.pk

        force_authenticate(self.request_put, user=self.user2)
        response = self.view_put(self.request_put, pk=id)

        self.assertNotEqual(response.data['created_by'], self.user2.pk)
        self.assertEqual(response.data['updated_by'], self.user2.pk)
<|endoftext|>


<commit_before>from django.conf import settings
from django.contrib.auth.models import User, Group
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import get_model
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _

from base.util import with_timestamp, with_author, remove_accents

# Competition-related models
@with_author
@with_timestamp
class Competition(models.Model):
    """
    Represents a competition. One roots site will usually hold several
    competitions, as there are usually several age categories or several
    subjects categories. Or both.
    """

    name = models.CharField(max_length=100,
                            verbose_name=_('competition name'),
                            unique=True)
    organizer_group = models.ForeignKey(Group,
                                        blank=True,
                                        null=True,
                                        verbose_name=_('organizer group'))

    # Fields added via foreign keys:

    #  competitionorgregistration_set
    #  competitionuserregistration_set
    #  gallery_set
    #  leaflet_set
    #  post_set
    #  problemset_set
    #  season_set
    #  user_set

    def __unicode__(self):
        return self.name

    def get_active_season(self):
        season_candidates = self.season_set.filter(start__lt=now(),
                                                   end__gt=now())
        if season_candidates.exists():
            return season_candidates[0]
        else:
            return None

    def get_all_user_seasons(self, user):
        """
        Yields the all the seasons the user competed in.
        """

        for season in self.season_set.all():
            if season.get_competitors().filter(pk=user.pk).exists():
                yield season

    def get_best_user_ranking(self, user):
        """
        Returns the best ranking of this user and the season in which
        it occured.

        Returns (None, None) if no user ranking found (user would have to never
        submit a solution).
        """

        best_rank = None
        best_season = None

        for season in self.get_all_user_seasons(user):
            rank = season.get_user_ranking(user)
            if best_rank < rank:
                best_rank = rank
                best_season = season

        return best_rank, best_season

    class Meta:
        ordering = ['name']
        verbose_name = _('competition')
        verbose_name_plural = _('competitions')

@with_author
@with_timestamp
class CompetitionUserRegistration(models.Model):
    """
    Represents a relation between user and competition. User himself can
    register into competition if he satisfies the conditions.
    """

    competition = models.ForeignKey('competitions.Competition',
                                    verbose_name=_('competition'))
    user = models.ForeignKey('auth.User',
                             verbose_name=_('user'))

    def __unicode__(self):
        return (self.user.__unicode__() + unicode(_(" competes in ")) +
               self.competition.__unicode__())

    class Meta:
        ordering = ['added_at']
        verbose_name = _('user registration')
        verbose_name_plural = _('user registrations')

@with_author
@with_timestamp
class CompetitionOrgRegistration(models.Model):
    """
    Represents a relation between organizer and comeptition. Organizer can
    help organize multiple competitions. Organizer registrations have to
    be approved.
    """

    competition = models.ForeignKey('competitions.Competition',
                                    verbose_name=_('competition'))
    organizer = models.ForeignKey('auth.User',
                                  verbose_name=_('organizer'))
    approved = models.BooleanField(verbose_name=_('registration approved'))

    def __unicode__(self):
        return (self.organizer.user.__unicode__() + unicode(_(" organizes ")) +
               self.competition.__unicode__())

    class Meta:
        ordering = ['added_at']
        verbose_name = _('organizer registration')
        verbose_name_plural = _('organizer registration')

class SeasonSeriesBaseMixin(object):

    def get_results(self):
        results = []

        for competitor in self.get_competitors():
            solutions, total = self.get_user_solutions_with_total(competitor)
            results.append((competitor, solutions, total))

        # Sort the results by total_score
        results.sort(key=lambda x: x[2], reverse=True)

        return results

    def get_user_ranking(self, user):
        """
        Returns the user's ranking in the current series. Since user can be
        ranked on multiple places, e.g. if 3 users achieved the perfect socre,
        the ranking is represented by a tuple (which would be (1,3) in this
        case). If only one user achiveed perfect score, the resulting ranking
        would be (1,1).
        """

        results = self.get_results()
        start = None

        for i in range(0, len(results)):
            competitor, solutions, total = results[i]
            if user == competitor:
                start = end = i + 1

            # This depends on the fact that the results list is sorted
            # decreasingly by the total score
            elif total == start:
                end = i + 1

        return (start, end)

    def get_user_percentile(self, user):
        """
        Returns user percentile (percentage of competitors that achived
        worse or equal ranking), as a float between 0 and 1.
        """
        all_competitors = len(self.get_competitors())
        user_ranking = self.get_user_ranking(user)[0]

        # Percentile can be expressed as 1 - (percentage of better ranked users)
        return 1.0 - (user_ranking - 1) / float(all_competitors)

    def get_user_by_ranking(self, rank):
        """
        Returns the set of users at the given rank (which can be empty).
        """

        results = self.get_results()

        # Convert rank to list index
        index = rank - 1

        users_at_rank = set()
        first_users_score = None

        if index < len(results):
            for competitor, solutions, total in results[index:]:

                # Mark the first user's score
                if first_users_score is None:
                    first_users_score = total

                # We add each user that has the same score until we reach
                # a lower score, at which point we break
                if total == first_users_score:
                    users_at_rank.add(competitor)
                else:
                    break

        return users_at_rank

@with_author
@with_timestamp
class Season(models.Model, SeasonSeriesBaseMixin):
    """
    Represents an one season of a competition. This is usually autumn or spring
    season. Using this model, however, we are not limited to 2 seasons per year.

    During each Season there might be several ProblemSets published as parts
    of that season.
    """

    competition = models.ForeignKey('competitions.Competition',
                                    verbose_name=_('competition'))
    year = models.IntegerField(verbose_name=_('year'))
    number = models.IntegerField(verbose_name=_('number'))
    name = models.CharField(max_length=50, verbose_name=_('name'))
    join_deadline = models.DateTimeField(blank=True,
                                         null=True,
                                         verbose_name=_('join deadline'))
    start = models.DateTimeField(verbose_name=_('season start'))
    end = models.DateTimeField(verbose_name=_('season end'))

    def get_competitors(self):
        """
        Returns the list of the competitors in the given season as everybody
        who submitted at least one problem solution in that season.
        """

        competitors = User.objects.none()

        for series in self.series_set.all():
            competitors = competitors | series.get_competitors()

        return competitors

    def get_user_solutions(self, user):
        """
        Returns the list of lists of solutions of problems in this season
        for a particular user, each series in a separate list.

        If the problem was not submitted, None is used instead of a
        UserSolution object.
        """

        return [series.get_user_solutions()
                for series in self.series_set.all()]

    def get_user_solutions_with_total(self, user):
        """
        Returns a list of lists of user solutions with total score computed.

        Also gives a total score per season for this user. A total score may
        be computed using settings.ROOTS_TOTAL_SEASON_SCORE_FUNC. Fallbacks
        to a simple sum of series subtotals if not specified.
        """

        all_solutions = []
        total = 0

        for series in self.series_set.all():
            solutions, subtotal = series.get_user_solutions_with_total(user)
            all_solutions.append(solutions)
            total += subtotal

        custom_total_func = getattr(settings,
                                    'ROOTS_TOTAL_SEASON_SCORE_FUNC',
                                    None)

        if custom_total_func is not None:
            assert callable(custom_total_func)
            return custom_total_func(user, solutions)
        else:
            # Fallback to simple sum
            return all_solutions, total

    def get_series_nearest_deadline(self):
        """
        Returns the most relevant series for the deadline. That is usually the
        series which is still active, but closest to its deadline.

        If all series are past their deadline, returns the last series.
        If there are no series in this season, returns None.
        """

        active_series = self.series_set.filter(submission_deadline__gt=now())

        if active_series.exists():
            return active_series[0]
        else:
            if self.series_set.exists():
                return self.series_set.order_by('-submission_deadline')[0]
            else:
                return None

    def __unicode__(self):
        template = "{name} ({competition} {year}-{number})"
        return template.format(competition=remove_accents(self.competition),
                               year=self.year,
                               number=self.number,
                               name=remove_accents(self.name),
                               )

    class Meta:
        ordering = ['competition', 'year', 'number']
        verbose_name = _('Season')
        verbose_name_plural = _('Seasons')

@with_author
@with_timestamp
class Series(models.Model, SeasonSeriesBaseMixin):
    """
    Represents one series of problems in the season of the competetion.
    """

    season = models.ForeignKey('competitions.Season',
                               verbose_name=_('season'))
    name = models.CharField(max_length=50,
                            verbose_name=_('name'))
    number = models.PositiveSmallIntegerField(verbose_name=_('number'))
    problemset = models.OneToOneField('problems.ProblemSet',
                                      blank=True,
                                      null=True,
                                      verbose_name=_('problem set assigned'))
    submission_deadline = models.DateTimeField(
                              verbose_name=_('series submission deadline'))
    is_active = models.BooleanField(default=False,
                                    verbose_name=_('is series active'))

    def get_competitors(self):
        """
        Returns the list of the competitors in the given series as everybody
        who submitted at least one problem solution.
        """

        competitors = User.objects.none()

        for problem in self.problemset.problems.all():
            problemset_competitors = User.objects.filter(
                usersolution__pk__in=problem.usersolution_set.all())
            competitors = competitors | problemset_competitors

        return competitors.distinct()

    def get_user_solutions(self, user):
        """
        Returns the list of solutions of problems in this series for a
        particular user. If the problem was not submitted, None is used
        instead of a UserSolution object.
        """

        solutions = []
        UserSolution = get_model('problems', 'UserSolution')

        # TODO: See if this can be done with a simple JOIN without making
        # multiple queries here. Not optimizing prematurely now though.

        for problem in self.problemset.problems.all():
            try:
                solution = UserSolution.objects.get(user=user, problem=problem)
                solutions.append(solution)
            except UserSolution.DoesNotExist:
                solutions.append(None)

        return solutions

    def get_user_solutions_with_total(self, user):
        """
        Returns a list of user solutions with total score computed.

        The computation of the total score can follow any arbitrary rules,
        it needs to be specified by the settings.ROOTS_SERIES_TOTAL_SCORE_FUNC.

        This defaults to simple sum.
        """

        solutions = self.get_user_solutions(user)

        custom_total_func = getattr(settings,
                                    'ROOTS_SERIES_TOTAL_SCORE_FUNC',
                                    None)

        if custom_total_func is not None:
            assert callable(custom_total_func)
            return custom_total_func(user, solutions)
        else:
            # Fallback to simple sum
            total_sum = sum([s.score or 0 for s in solutions if s is not None])
            return solutions, total_sum

    def is_past_submission_deadline(self):
        return now() > self.submission_deadline

    def is_nearest_deadline(self):
        # Series are returned sorted by the submission deadline
        return self == self.season.get_series_nearest_deadline()

    def clean(self, *args, **kwargs):
        if self.is_active:
            if not self.submission_deadline:
                raise ValidationError("Submission deadline must be set to "
                                      "make the series active")
            if not self.problemset:
                raise ValidationError("Corresponding set of problems must be "
                                      "set to make the series active")

            if self.is_past_submission_deadline():
                raise ValidationError("Series that is past its submission "
                                      "deadline cannot be made active")

        super(Series, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.name

    class Meta:

        ordering = ['submission_deadline']
        unique_together = ('season', 'number')
        verbose_name = _('Series')
        verbose_name_plural = _('Series')
<commit_msg>competitions: Add year segment mapping to Season<commit_after>from django.conf import settings
from django.contrib.auth.models import User, Group
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import get_model
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _

from base.util import (with_timestamp, with_author, remove_accents,
                       YearSegment)

# Competition-related models
@with_author
@with_timestamp
class Competition(models.Model):
    """
    Represents a competition. One roots site will usually hold several
    competitions, as there are usually several age categories or several
    subjects categories. Or both.
    """

    name = models.CharField(max_length=100,
                            verbose_name=_('competition name'),
                            unique=True)
    organizer_group = models.ForeignKey(Group,
                                        blank=True,
                                        null=True,
                                        verbose_name=_('organizer group'))

    # Fields added via foreign keys:

    #  competitionorgregistration_set
    #  competitionuserregistration_set
    #  gallery_set
    #  leaflet_set
    #  post_set
    #  problemset_set
    #  season_set
    #  user_set

    def __unicode__(self):
        return self.name

    def get_active_season(self):
        season_candidates = self.season_set.filter(start__lt=now(),
                                                   end__gt=now())
        if season_candidates.exists():
            return season_candidates[0]
        else:
            return None

    def get_all_user_seasons(self, user):
        """
        Yields the all the seasons the user competed in.
        """

        for season in self.season_set.all():
            if season.get_competitors().filter(pk=user.pk).exists():
                yield season

    def get_best_user_ranking(self, user):
        """
        Returns the best ranking of this user and the season in which
        it occured.

        Returns (None, None) if no user ranking found (user would have to never
        submit a solution).
        """

        best_rank = None
        best_season = None

        for season in self.get_all_user_seasons(user):
            rank = season.get_user_ranking(user)
            if best_rank < rank:
                best_rank = rank
                best_season = season

        return best_rank, best_season

    class Meta:
        ordering = ['name']
        verbose_name = _('competition')
        verbose_name_plural = _('competitions')

@with_author
@with_timestamp
class CompetitionUserRegistration(models.Model):
    """
    Represents a relation between user and competition. User himself can
    register into competition if he satisfies the conditions.
    """

    competition = models.ForeignKey('competitions.Competition',
                                    verbose_name=_('competition'))
    user = models.ForeignKey('auth.User',
                             verbose_name=_('user'))

    def __unicode__(self):
        return (self.user.__unicode__() + unicode(_(" competes in ")) +
               self.competition.__unicode__())

    class Meta:
        ordering = ['added_at']
        verbose_name = _('user registration')
        verbose_name_plural = _('user registrations')

@with_author
@with_timestamp
class CompetitionOrgRegistration(models.Model):
    """
    Represents a relation between organizer and comeptition. Organizer can
    help organize multiple competitions. Organizer registrations have to
    be approved.
    """

    competition = models.ForeignKey('competitions.Competition',
                                    verbose_name=_('competition'))
    organizer = models.ForeignKey('auth.User',
                                  verbose_name=_('organizer'))
    approved = models.BooleanField(verbose_name=_('registration approved'))

    def __unicode__(self):
        return (self.organizer.user.__unicode__() + unicode(_(" organizes ")) +
               self.competition.__unicode__())

    class Meta:
        ordering = ['added_at']
        verbose_name = _('organizer registration')
        verbose_name_plural = _('organizer registration')

class SeasonSeriesBaseMixin(object):

    def get_results(self):
        results = []

        for competitor in self.get_competitors():
            solutions, total = self.get_user_solutions_with_total(competitor)
            results.append((competitor, solutions, total))

        # Sort the results by total_score
        results.sort(key=lambda x: x[2], reverse=True)

        return results

    def get_user_ranking(self, user):
        """
        Returns the user's ranking in the current series. Since user can be
        ranked on multiple places, e.g. if 3 users achieved the perfect socre,
        the ranking is represented by a tuple (which would be (1,3) in this
        case). If only one user achiveed perfect score, the resulting ranking
        would be (1,1).
        """

        results = self.get_results()
        start = None

        for i in range(0, len(results)):
            competitor, solutions, total = results[i]
            if user == competitor:
                start = end = i + 1

            # This depends on the fact that the results list is sorted
            # decreasingly by the total score
            elif total == start:
                end = i + 1

        return (start, end)

    def get_user_percentile(self, user):
        """
        Returns user percentile (percentage of competitors that achived
        worse or equal ranking), as a float between 0 and 1.
        """
        all_competitors = len(self.get_competitors())
        user_ranking = self.get_user_ranking(user)[0]

        # Percentile can be expressed as 1 - (percentage of better ranked users)
        return 1.0 - (user_ranking - 1) / float(all_competitors)

    def get_user_by_ranking(self, rank):
        """
        Returns the set of users at the given rank (which can be empty).
        """

        results = self.get_results()

        # Convert rank to list index
        index = rank - 1

        users_at_rank = set()
        first_users_score = None

        if index < len(results):
            for competitor, solutions, total in results[index:]:

                # Mark the first user's score
                if first_users_score is None:
                    first_users_score = total

                # We add each user that has the same score until we reach
                # a lower score, at which point we break
                if total == first_users_score:
                    users_at_rank.add(competitor)
                else:
                    break

        return users_at_rank

@with_author
@with_timestamp
class Season(models.Model, SeasonSeriesBaseMixin):
    """
    Represents an one season of a competition. This is usually autumn or spring
    season. Using this model, however, we are not limited to 2 seasons per year.

    During each Season there might be several ProblemSets published as parts
    of that season.
    """

    competition = models.ForeignKey('competitions.Competition',
                                    verbose_name=_('competition'))
    year = models.IntegerField(verbose_name=_('year'))
    number = models.IntegerField(verbose_name=_('number'))
    name = models.CharField(max_length=50, verbose_name=_('name'))
    join_deadline = models.DateTimeField(blank=True,
                                         null=True,
                                         verbose_name=_('join deadline'))
    start = models.DateTimeField(verbose_name=_('season start'))
    end = models.DateTimeField(verbose_name=_('season end'))

    def get_year_segment(self, num_segments=2):
        """
        Returns the year segment this season belongs to.
        """

        season_midpoint = (self.start + (self.end - self.start) / 2).date()

        return YearSegment.by_date(season_midpoint, num_segments)

    def get_competitors(self):
        """
        Returns the list of the competitors in the given season as everybody
        who submitted at least one problem solution in that season.
        """

        competitors = User.objects.none()

        for series in self.series_set.all():
            competitors = competitors | series.get_competitors()

        return competitors

    def get_user_solutions(self, user):
        """
        Returns the list of lists of solutions of problems in this season
        for a particular user, each series in a separate list.

        If the problem was not submitted, None is used instead of a
        UserSolution object.
        """

        return [series.get_user_solutions()
                for series in self.series_set.all()]

    def get_user_solutions_with_total(self, user):
        """
        Returns a list of lists of user solutions with total score computed.

        Also gives a total score per season for this user. A total score may
        be computed using settings.ROOTS_TOTAL_SEASON_SCORE_FUNC. Fallbacks
        to a simple sum of series subtotals if not specified.
        """

        all_solutions = []
        total = 0

        for series in self.series_set.all():
            solutions, subtotal = series.get_user_solutions_with_total(user)
            all_solutions.append(solutions)
            total += subtotal

        custom_total_func = getattr(settings,
                                    'ROOTS_TOTAL_SEASON_SCORE_FUNC',
                                    None)

        if custom_total_func is not None:
            assert callable(custom_total_func)
            return custom_total_func(user, solutions)
        else:
            # Fallback to simple sum
            return all_solutions, total

    def get_series_nearest_deadline(self):
        """
        Returns the most relevant series for the deadline. That is usually the
        series which is still active, but closest to its deadline.

        If all series are past their deadline, returns the last series.
        If there are no series in this season, returns None.
        """

        active_series = self.series_set.filter(submission_deadline__gt=now())

        if active_series.exists():
            return active_series[0]
        else:
            if self.series_set.exists():
                return self.series_set.order_by('-submission_deadline')[0]
            else:
                return None

    def __unicode__(self):
        template = "{name} ({competition} {year}-{number})"
        return template.format(competition=remove_accents(self.competition),
                               year=self.year,
                               number=self.number,
                               name=remove_accents(self.name),
                               )

    class Meta:
        ordering = ['competition', 'year', 'number']
        verbose_name = _('Season')
        verbose_name_plural = _('Seasons')

@with_author
@with_timestamp
class Series(models.Model, SeasonSeriesBaseMixin):
    """
    Represents one series of problems in the season of the competetion.
    """

    season = models.ForeignKey('competitions.Season',
                               verbose_name=_('season'))
    name = models.CharField(max_length=50,
                            verbose_name=_('name'))
    number = models.PositiveSmallIntegerField(verbose_name=_('number'))
    problemset = models.OneToOneField('problems.ProblemSet',
                                      blank=True,
                                      null=True,
                                      verbose_name=_('problem set assigned'))
    submission_deadline = models.DateTimeField(
                              verbose_name=_('series submission deadline'))
    is_active = models.BooleanField(default=False,
                                    verbose_name=_('is series active'))

    def get_competitors(self):
        """
        Returns the list of the competitors in the given series as everybody
        who submitted at least one problem solution.
        """

        competitors = User.objects.none()

        for problem in self.problemset.problems.all():
            problemset_competitors = User.objects.filter(
                usersolution__pk__in=problem.usersolution_set.all())
            competitors = competitors | problemset_competitors

        return competitors.distinct()

    def get_user_solutions(self, user):
        """
        Returns the list of solutions of problems in this series for a
        particular user. If the problem was not submitted, None is used
        instead of a UserSolution object.
        """

        solutions = []
        UserSolution = get_model('problems', 'UserSolution')

        # TODO: See if this can be done with a simple JOIN without making
        # multiple queries here. Not optimizing prematurely now though.

        for problem in self.problemset.problems.all():
            try:
                solution = UserSolution.objects.get(user=user, problem=problem)
                solutions.append(solution)
            except UserSolution.DoesNotExist:
                solutions.append(None)

        return solutions

    def get_user_solutions_with_total(self, user):
        """
        Returns a list of user solutions with total score computed.

        The computation of the total score can follow any arbitrary rules,
        it needs to be specified by the settings.ROOTS_SERIES_TOTAL_SCORE_FUNC.

        This defaults to simple sum.
        """

        solutions = self.get_user_solutions(user)

        custom_total_func = getattr(settings,
                                    'ROOTS_SERIES_TOTAL_SCORE_FUNC',
                                    None)

        if custom_total_func is not None:
            assert callable(custom_total_func)
            return custom_total_func(user, solutions)
        else:
            # Fallback to simple sum
            total_sum = sum([s.score or 0 for s in solutions if s is not None])
            return solutions, total_sum

    def is_past_submission_deadline(self):
        return now() > self.submission_deadline

    def is_nearest_deadline(self):
        # Series are returned sorted by the submission deadline
        return self == self.season.get_series_nearest_deadline()

    def clean(self, *args, **kwargs):
        if self.is_active:
            if not self.submission_deadline:
                raise ValidationError("Submission deadline must be set to "
                                      "make the series active")
            if not self.problemset:
                raise ValidationError("Corresponding set of problems must be "
                                      "set to make the series active")

            if self.is_past_submission_deadline():
                raise ValidationError("Series that is past its submission "
                                      "deadline cannot be made active")

        super(Series, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.name

    class Meta:

        ordering = ['submission_deadline']
        unique_together = ('season', 'number')
        verbose_name = _('Series')
        verbose_name_plural = _('Series')
<|endoftext|>


<commit_before>from openerp import api, models, fields, SUPERUSER_ID
from openerp.http import request
import fnmatch
import werkzeug.utils
from werkzeug.exceptions import NotFound

class website_redirect(models.Model):
    _name = 'website.redirect'

    _order = 'sequence,id'

    name = fields.Char('Name')
    active = fields.Boolean('Active', default=True)
    sequence = fields.Integer('Sequence')
    domain = fields.Char('Domain Name', placeholder='odoo.com', help='keep empty to apply rules for any domain')
    case_sensitive = fields.Boolean('Case-sensitive', default=False)

    rule_ids = fields.One2many('website.redirect.rule', 'redirect_id', string='Rules')

class website_redirect(models.Model):
    _name = 'website.redirect.rule'

    _order = 'sequence,id'
    sequence = fields.Integer('Sequence')
    pattern = fields.Char('From', help='Unix shell-style wildcards. Check https://docs.python.org/2/library/fnmatch.html for details', required=True)
    target = fields.Char('To', required=True)
    redirect_id = fields.Many2one('website.redirect')

class ir_http(models.AbstractModel):
    _inherit = 'ir.http'

    def _handle_exception(self, exception, code=500):
        if code == 404 or isinstance(exception, NotFound):
            redirect = self.try_redirect()
            if redirect:
                return redirect
        return super(ir_http, self)._handle_exception(exception, code=code)

    def try_redirect(self):
        host = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
        www, _, h = host.partition('.')
        if www == 'www':
            host = h
        for redirect in request.env['website.redirect'].sudo().search(['|', ('domain', '=', False), ('domain', '=', host)]):
            path = request.httprequest.path
            if not redirect.case_sensitive:
                path = path.lower()
            for rule in redirect.rule_ids:
                pattern = rule.pattern
                if not redirect.case_sensitive:
                    pattern = pattern.lower()
                if fnmatch.fnmatch(path, pattern):
                    code = 302
                    return werkzeug.utils.redirect(rule.target, code)
        return None
<commit_msg>[FIX] rules with query_string<commit_after>from openerp import api, models, fields, SUPERUSER_ID
from openerp.http import request
import fnmatch
import werkzeug.utils
from werkzeug.exceptions import NotFound

class website_redirect(models.Model):
    _name = 'website.redirect'

    _order = 'sequence,id'

    name = fields.Char('Name')
    active = fields.Boolean('Active', default=True)
    sequence = fields.Integer('Sequence')
    domain = fields.Char('Domain Name', placeholder='odoo.com', help='keep empty to apply rules for any domain')
    case_sensitive = fields.Boolean('Case-sensitive', default=False)

    rule_ids = fields.One2many('website.redirect.rule', 'redirect_id', string='Rules')

class website_redirect(models.Model):
    _name = 'website.redirect.rule'

    _order = 'sequence,id'
    sequence = fields.Integer('Sequence')
    pattern = fields.Char('From', help='Unix shell-style wildcards. Check https://docs.python.org/2/library/fnmatch.html for details', required=True)
    target = fields.Char('To', required=True)
    redirect_id = fields.Many2one('website.redirect')

class ir_http(models.AbstractModel):
    _inherit = 'ir.http'

    def _handle_exception(self, exception, code=500):
        if code == 404 or isinstance(exception, NotFound):
            redirect = self.try_redirect()
            if redirect:
                return redirect
        return super(ir_http, self)._handle_exception(exception, code=code)

    def try_redirect(self):
        host = request.httprequest.environ.get('HTTP_HOST', '').split(':')[0]
        www, _, h = host.partition('.')
        if www == 'www':
            host = h
        for redirect in request.env['website.redirect'].sudo().search(['|', ('domain', '=', False), ('domain', '=', host)]):
            query_string = request.httprequest.query_string
            path = request.httprequest.path
            if query_string:
                path = '%s?%s' % (path, query_string)
            if not redirect.case_sensitive:
                path = path.lower()
            for rule in redirect.rule_ids:
                pattern = rule.pattern
                if not redirect.case_sensitive:
                    pattern = pattern.lower()
                if fnmatch.fnmatch(path, pattern):
                    code = 302
                    return werkzeug.utils.redirect(rule.target, code)
        return None
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

"""
Copyright [2009-2017] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import unittest as ut

from Bio import SeqIO

from ensembl import EnsemblImporter

class BaseTest(ut.TestCase):
    filename = 'data/Caenorhabditis_elegans.WBcel235.87.chromosome.IV.dat'

    @classmethod
    def setUpClass(cls):
        cls.features = {}
        cls.record = SeqIO.read(cls.filename, 'embl')
        for feature in cls.record.features:
            gene = feature.qualifiers.get('gene', [None])[0]
            key = (feature.type, gene)
            cls.features[key] = feature

    def setUp(self):
        self.importer = EnsemblImporter(input_file=self.filename)

class FeatureParsingTest(BaseTest):
    def __getattr__(self, key):
        if hasattr(self.importer, key):
            fn = getattr(self.importer, key)
            return lambda *key: fn(self.features[key])
        raise AttributeError("Unknown attribute %s" % key)

class BothParsingTest(BaseTest):
    def __getattr__(self, key):
        if hasattr(self.importer, key):
            annotation = self.importer.standard_annotations(self.record)
            fn = getattr(self.importer, key)
            return lambda *key: fn(annotation, self.features[key])
        raise AttributeError("Unknown attribute %s" % key)

class SimpleTests(FeatureParsingTest):

    def test_can_detect_if_is_noncoding(self):
        assert self.is_ncrna('gene', 'WBGene00021406') is False
        assert self.is_ncrna('misc_RNA', 'WBGene00195502') is True

    def test_can_detect_if_is_pseudogene(self):
        assert self.is_pseudogene('misc_RNA', 'WBGene00023163') is True
        assert self.is_pseudogene('gene', 'WBGene00001103') is False

    def test_it_can_get_all_standard_annotations(self):
        assert self.importer.standard_annotations(self.record) == {
            'database': 'ENSEMBL',
            'lineage': (
                'Eukaryota; Opisthokonta; Metazoa; Eumetazoa; Bilateria; '
                'Protostomia; Ecdysozoa; Nematoda; Chromadorea; Rhabditida; '
                'Rhabditoidea; Rhabditidae; Peloderinae'
            ),
            'mol_type': 'genomic DNA',
            'pseudogene': 'N',
            'parent_accession': "IV.WBcel235",
            'seq_version': '',
            'common_name': 'C.elegans',
            'species': "Caenorhabditis elegans",
            'ncbi_tax_id':  6239,
            'is_composite': 'N',
            'accession': None,
            'references': [{
                'authors': "Andrew Yates, Wasiu Akanni, M. Ridwan Amode, Daniel Barrell, Konstantinos Billis, Denise Carvalho-Silva, Carla Cummins, Peter Clapham, Stephen Fitzgerald, Laurent Gil1 Carlos Garcín Girón, Leo Gordon, Thibaut Hourlier, Sarah E. Hunt, Sophie H. Janacek, Nathan Johnson, Thomas Juettemann, Stephen Keenan, Ilias Lavidas, Fergal J. Martin, Thomas Maurel, William McLaren, Daniel N. Murphy, Rishi Nag, Michael Nuhn, Anne Parker, Mateus Patricio, Miguel Pignatelli, Matthew Rahtz, Harpreet Singh Riat, Daniel Sheppard, Kieron Taylor, Anja Thormann, Alessandro Vullo, Steven P. Wilder, Amonida Zadissa, Ewan Birney, Jennifer Harrow, Matthieu Muffato, Emily Perry, Magali Ruffier, Giulietta Spudich, Stephen J. Trevanion, Fiona Cunningham, Bronwen L. Aken, Daniel R. Zerbino, Paul Flicek",
                'location': "Nucleic Acids Res. 2016 44 Database issue:D710-6",
                'title': "Ensembl 2016",
                'pmid': 26687719,
                'doi': "10.1093/nar/gkv115",
            }],
            'sequence': self.record.seq,
        }

    def test_can_get_transcript_id(self):
        assert self.transcript('mRNA', "WBGene00021408") == 'Y38C1AB.6'

    def test_can_get_ncrna_type(self):
        assert self.ncrna('misc_RNA', "WBGene00195502") == 'ncRNA'

    def test_can_get_notes_without_ncrna(self):
        assert self.note('misc_RNA', "WBGene00195502") == [
            "transcript_id=Y38C1AB.9",
        ]

    def test_can_create_easy_locations(self):
        assert self.assembly_info('misc_RNA', "WBGene00195502") == [
            {'complement': False, 'primary_start': 41471, 'primary_end': 41620}
        ]

    def test_can_get_joined_locations(self):
        assert self.assembly_info('mRNA', 'WBGene00235257') == [
             {'complement': True, 'primary_start': 53543, 'primary_end': 53631},
             {'complement': True, 'primary_start': 53401, 'primary_end': 53485},
        ]

    def test_can_get_gene_id(self):
        assert self.gene('misc_RNA', "WBGene00166500") == "WBGene00166500"

class LongSpeciesTests(FeatureParsingTest):
    filename = 'data/test_species_patch.ncr'

    def test_can_load_large_organism_name(self):
        """This test is meant to see if the biopython reader can handle parsing
        an EMBL file where the organism name may span more than one name.
        """

        assert self.importer.standard_annotations(self.record) == {
            'database': 'ENSEMBL',
            'lineage': (
                'Eukaryota; Fungi; Dikarya; Basidiomycota; Agaricomycotina; '
                'Agaricomycetes; Russulales; Russulaceae; Russula; '
                'environmental samples'
            ),
            'mol_type': 'genomic DNA',
            'pseudogene': 'N',
            'parent_accession': "EF411133.1:1..726:misc_RNA",
            'seq_version': '',
            'common_name': 'Russula brevipes var. acrior',
            'species': 'uncultured ectomycorrhiza',
            'ncbi_tax_id':  446167,
            'is_composite': 'N',
            'accession': None,

            # Note this is *NOT* the reference in the file and this is on
            # purpose. The parser here is not a general EMBL format parser, but
            # a parser for the data produced by ENSEMBL. For this reason we
            # only use a default reference, and not what may be in the file.
            'references': [{
                'authors': """Andrew Yates, Wasiu Akanni, M. Ridwan Amode, Daniel Barrell, Konstantinos Billis, Denise Carvalho-Silva, Carla Cummins, Peter Clapham, Stephen Fitzgerald, Laurent Gil1 Carlos Garcín Girón, Leo Gordon, Thibaut Hourlier, Sarah E. Hunt, Sophie H. Janacek, Nathan Johnson, Thomas Juettemann, Stephen Keenan, Ilias Lavidas, Fergal J. Martin, Thomas Maurel, William McLaren, Daniel N. Murphy, Rishi Nag, Michael Nuhn, Anne Parker, Mateus Patricio, Miguel Pignatelli, Matthew Rahtz, Harpreet Singh Riat, Daniel Sheppard, Kieron Taylor, Anja Thormann, Alessandro Vullo, Steven P. Wilder, Amonida Zadissa, Ewan Birney, Jennifer Harrow, Matthieu Muffato, Emily Perry, Magali Ruffier, Giulietta Spudich, Stephen J. Trevanion, Fiona Cunningham, Bronwen L. Aken, Daniel R. Zerbino, Paul Flicek""",
                'location': "Nucleic Acids Res. 2016 44 Database issue:D710-6",
                'title': "Ensembl 2016",
                'pmid': 26687719,
                'doi': "10.1093/nar/gkv115",
            }],
            'sequence': self.record.seq,
        }

class LoadingTests(FeatureParsingTest):

    def entry(self, *key):
        return self.entry_specific_data(*key)

    def test_produces_correct_entries(self):
        assert self.entry('misc_RNA', "WBGene00166500") == {
            'assembly_info': [{
                'complement': False,
                'primary_start': 58691,
                'primary_end': 58711,
            }],
            'db_xrefs': [
                "RefSeq_ncRNA:NR_052854",
                "wormbase_transcript:T05C7.2",
            ],
            'feature_location_end': 58711,
            'feature_location_start': 58691,
            'feature_type': 'misc_RNA',
            'gene': "WBGene00166500",
            'ncrna_class': 'piRNA',
            'note': ['transcript_id=T05C7.2'],
            'locus_tag': '',
            'primary_id': 'T05C7.2',
        }

    # def test_can_get_xrefs(self):
    #     assert self.entry('misc_RNA', "ENSTGUG00000017886.1") == {
    #         'assembly_info': [
    #             {'complement': True, 'primary_start': 43202481, 'primary_end': 43202567},
    #         ],
    #         'db_xrefs': ["RNACentral:URS000075DEE3"],
    #         'feature_location_end': 43202567,
    #         'feature_location_start': 43202481,
    #         'feature_type': 'misc_RNA',
    #         'gene': "ENSTGUG00000017886.1",
    #         'ncrna_class': 'miRNA',
    #         'note': ["transcript_id=ENSTGUT00000018573.1"],
    #         'locus_tag': '',
    #         'primary_id': 'ENSTGUT00000018573.1',
    #     }

    # def test_can_have_several_xrefs(self):
    #     assert self.entry('misc_RNA', "ENSTGUG00000018724.1") == {
    #         'assembly_info': [
    #             {'complement': False, 'primary_start': 48906815, 'primary_end': 48906895},
    #         ],
    #         'db_xrefs': ["RFAM_trans_name:SNORD102-201",
    #                      "RNACentral:URS0000661E55"],
    #         'feature_location_end': 48906895,
    #         'feature_location_start': 48906815,
    #         'feature_type': 'misc_RNA',
    #         'gene': "ENSTGUG00000018724.1",
    #         'ncrna_class': 'snoRNA',
    #         'note': ["transcript_id=ENSTGUT00000019440.1"],
    #         'locus_tag': '',
    #         'primary_id': 'ENSTGUT00000019440.1',
    #     }

class CompleteParsingTest(BothParsingTest):
    def test_can_create_reasonable_description(self):
        assert self.description('misc_RNA', "WBGene00166500") == \
            "Caenorhabditis elegans (C.elegans) piRNA"

    def test_can_create_reasonable_primary_id(self):
        assert self.primary_id('misc_RNA', "WBGene00202392") == \
            "IV.WBcel235:cTel79B.2:ncRNA"

    def test_produces_valid_data(self):
        entry = self.rnacentral_entry('misc_RNA', "WBGene00198969")
        assert entry.sequence == 'TTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGG'
        assert entry.is_valid(verbose=True) is True

    def test_can_loads_all_non_coding_rnas(self):
        data = self.importer.data(self.filename)
        assert len(list(data)) == 16272
<commit_msg>Add tests for ensure we get all tRNA<commit_after># -*- coding: utf-8 -*-

"""
Copyright [2009-2017] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import unittest as ut
from collections import Counter

from Bio import SeqIO

from ensembl import EnsemblImporter

class BaseTest(ut.TestCase):
    filename = 'data/Caenorhabditis_elegans.WBcel235.87.chromosome.IV.dat'

    @classmethod
    def setUpClass(cls):
        cls.features = {}
        cls.record = SeqIO.read(cls.filename, 'embl')
        for feature in cls.record.features:
            gene = feature.qualifiers.get('gene', [None])[0]
            key = (feature.type, gene)
            cls.features[key] = feature

    def setUp(self):
        self.importer = EnsemblImporter(input_file=self.filename)

class FeatureParsingTest(BaseTest):
    def __getattr__(self, key):
        if hasattr(self.importer, key):
            fn = getattr(self.importer, key)
            return lambda *key: fn(self.features[key])
        raise AttributeError("Unknown attribute %s" % key)

class BothParsingTest(BaseTest):
    def __getattr__(self, key):
        if hasattr(self.importer, key):
            annotation = self.importer.standard_annotations(self.record)
            fn = getattr(self.importer, key)
            return lambda *key: fn(annotation, self.features[key])
        raise AttributeError("Unknown attribute %s" % key)

class SimpleTests(FeatureParsingTest):

    def test_can_detect_if_is_noncoding(self):
        assert self.is_ncrna('gene', 'WBGene00021406') is False
        assert self.is_ncrna('misc_RNA', 'WBGene00195502') is True

    def test_can_detect_if_is_pseudogene(self):
        assert self.is_pseudogene('misc_RNA', 'WBGene00023163') is True
        assert self.is_pseudogene('gene', 'WBGene00001103') is False

    def test_it_can_get_all_standard_annotations(self):
        assert self.importer.standard_annotations(self.record) == {
            'database': 'ENSEMBL',
            'lineage': (
                'Eukaryota; Opisthokonta; Metazoa; Eumetazoa; Bilateria; '
                'Protostomia; Ecdysozoa; Nematoda; Chromadorea; Rhabditida; '
                'Rhabditoidea; Rhabditidae; Peloderinae'
            ),
            'mol_type': 'genomic DNA',
            'pseudogene': 'N',
            'parent_accession': "IV.WBcel235",
            'seq_version': '',
            'common_name': 'C.elegans',
            'species': "Caenorhabditis elegans",
            'ncbi_tax_id':  6239,
            'is_composite': 'N',
            'accession': None,
            'references': [{
                'authors': "Andrew Yates, Wasiu Akanni, M. Ridwan Amode, Daniel Barrell, Konstantinos Billis, Denise Carvalho-Silva, Carla Cummins, Peter Clapham, Stephen Fitzgerald, Laurent Gil1 Carlos Garcín Girón, Leo Gordon, Thibaut Hourlier, Sarah E. Hunt, Sophie H. Janacek, Nathan Johnson, Thomas Juettemann, Stephen Keenan, Ilias Lavidas, Fergal J. Martin, Thomas Maurel, William McLaren, Daniel N. Murphy, Rishi Nag, Michael Nuhn, Anne Parker, Mateus Patricio, Miguel Pignatelli, Matthew Rahtz, Harpreet Singh Riat, Daniel Sheppard, Kieron Taylor, Anja Thormann, Alessandro Vullo, Steven P. Wilder, Amonida Zadissa, Ewan Birney, Jennifer Harrow, Matthieu Muffato, Emily Perry, Magali Ruffier, Giulietta Spudich, Stephen J. Trevanion, Fiona Cunningham, Bronwen L. Aken, Daniel R. Zerbino, Paul Flicek",
                'location': "Nucleic Acids Res. 2016 44 Database issue:D710-6",
                'title': "Ensembl 2016",
                'pmid': 26687719,
                'doi': "10.1093/nar/gkv115",
            }],
            'sequence': self.record.seq,
        }

    def test_can_get_transcript_id(self):
        assert self.transcript('mRNA', "WBGene00021408") == 'Y38C1AB.6'

    def test_can_get_ncrna_type(self):
        assert self.ncrna('misc_RNA', "WBGene00195502") == 'ncRNA'

    def test_can_get_notes_without_ncrna(self):
        assert self.note('misc_RNA', "WBGene00195502") == [
            "transcript_id=Y38C1AB.9",
        ]

    def test_can_create_easy_locations(self):
        assert self.assembly_info('misc_RNA', "WBGene00195502") == [
            {'complement': False, 'primary_start': 41471, 'primary_end': 41620}
        ]

    def test_can_get_joined_locations(self):
        assert self.assembly_info('mRNA', 'WBGene00235257') == [
             {'complement': True, 'primary_start': 53543, 'primary_end': 53631},
             {'complement': True, 'primary_start': 53401, 'primary_end': 53485},
        ]

    def test_can_get_gene_id(self):
        assert self.gene('misc_RNA', "WBGene00166500") == "WBGene00166500"

class LongSpeciesTests(FeatureParsingTest):
    filename = 'data/test_species_patch.ncr'

    def test_can_load_large_organism_name(self):
        """This test is meant to see if the biopython reader can handle parsing
        an EMBL file where the organism name may span more than one name.
        """

        assert self.importer.standard_annotations(self.record) == {
            'database': 'ENSEMBL',
            'lineage': (
                'Eukaryota; Fungi; Dikarya; Basidiomycota; Agaricomycotina; '
                'Agaricomycetes; Russulales; Russulaceae; Russula; '
                'environmental samples'
            ),
            'mol_type': 'genomic DNA',
            'pseudogene': 'N',
            'parent_accession': "EF411133.1:1..726:misc_RNA",
            'seq_version': '',
            'common_name': 'Russula brevipes var. acrior',
            'species': 'uncultured ectomycorrhiza',
            'ncbi_tax_id':  446167,
            'is_composite': 'N',
            'accession': None,

            # Note this is *NOT* the reference in the file and this is on
            # purpose. The parser here is not a general EMBL format parser, but
            # a parser for the data produced by ENSEMBL. For this reason we
            # only use a default reference, and not what may be in the file.
            'references': [{
                'authors': """Andrew Yates, Wasiu Akanni, M. Ridwan Amode, Daniel Barrell, Konstantinos Billis, Denise Carvalho-Silva, Carla Cummins, Peter Clapham, Stephen Fitzgerald, Laurent Gil1 Carlos Garcín Girón, Leo Gordon, Thibaut Hourlier, Sarah E. Hunt, Sophie H. Janacek, Nathan Johnson, Thomas Juettemann, Stephen Keenan, Ilias Lavidas, Fergal J. Martin, Thomas Maurel, William McLaren, Daniel N. Murphy, Rishi Nag, Michael Nuhn, Anne Parker, Mateus Patricio, Miguel Pignatelli, Matthew Rahtz, Harpreet Singh Riat, Daniel Sheppard, Kieron Taylor, Anja Thormann, Alessandro Vullo, Steven P. Wilder, Amonida Zadissa, Ewan Birney, Jennifer Harrow, Matthieu Muffato, Emily Perry, Magali Ruffier, Giulietta Spudich, Stephen J. Trevanion, Fiona Cunningham, Bronwen L. Aken, Daniel R. Zerbino, Paul Flicek""",
                'location': "Nucleic Acids Res. 2016 44 Database issue:D710-6",
                'title': "Ensembl 2016",
                'pmid': 26687719,
                'doi': "10.1093/nar/gkv115",
            }],
            'sequence': self.record.seq,
        }

class LoadingTests(FeatureParsingTest):

    def entry(self, *key):
        return self.entry_specific_data(*key)

    def test_produces_correct_entries(self):
        assert self.entry('misc_RNA', "WBGene00166500") == {
            'assembly_info': [{
                'complement': False,
                'primary_start': 58691,
                'primary_end': 58711,
            }],
            'db_xrefs': [
                "RefSeq_ncRNA:NR_052854",
                "wormbase_transcript:T05C7.2",
            ],
            'feature_location_end': 58711,
            'feature_location_start': 58691,
            'feature_type': 'misc_RNA',
            'gene': "WBGene00166500",
            'ncrna_class': 'piRNA',
            'note': ['transcript_id=T05C7.2'],
            'locus_tag': '',
            'primary_id': 'T05C7.2',
        }

    # def test_can_get_xrefs(self):
    #     assert self.entry('misc_RNA', "ENSTGUG00000017886.1") == {
    #         'assembly_info': [
    #             {'complement': True, 'primary_start': 43202481, 'primary_end': 43202567},
    #         ],
    #         'db_xrefs': ["RNACentral:URS000075DEE3"],
    #         'feature_location_end': 43202567,
    #         'feature_location_start': 43202481,
    #         'feature_type': 'misc_RNA',
    #         'gene': "ENSTGUG00000017886.1",
    #         'ncrna_class': 'miRNA',
    #         'note': ["transcript_id=ENSTGUT00000018573.1"],
    #         'locus_tag': '',
    #         'primary_id': 'ENSTGUT00000018573.1',
    #     }

    # def test_can_have_several_xrefs(self):
    #     assert self.entry('misc_RNA', "ENSTGUG00000018724.1") == {
    #         'assembly_info': [
    #             {'complement': False, 'primary_start': 48906815, 'primary_end': 48906895},
    #         ],
    #         'db_xrefs': ["RFAM_trans_name:SNORD102-201",
    #                      "RNACentral:URS0000661E55"],
    #         'feature_location_end': 48906895,
    #         'feature_location_start': 48906815,
    #         'feature_type': 'misc_RNA',
    #         'gene': "ENSTGUG00000018724.1",
    #         'ncrna_class': 'snoRNA',
    #         'note': ["transcript_id=ENSTGUT00000019440.1"],
    #         'locus_tag': '',
    #         'primary_id': 'ENSTGUT00000019440.1',
    #     }

class CompleteParsingTest(BothParsingTest):
    def test_can_create_reasonable_description(self):
        assert self.description('misc_RNA', "WBGene00166500") == \
            "Caenorhabditis elegans (C.elegans) piRNA"

    def test_can_create_reasonable_primary_id(self):
        assert self.primary_id('misc_RNA', "WBGene00202392") == \
            "IV.WBcel235:cTel79B.2:ncRNA"

    def test_produces_valid_data(self):
        entry = self.rnacentral_entry('misc_RNA', "WBGene00198969")
        assert entry.sequence == 'TTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGGCTTAGG'
        assert entry.is_valid(verbose=True) is True

    def test_can_loads_all_non_coding_rnas(self):
        data = self.importer.data(self.filename)
        assert len(list(data)) == 16272

class TRNATest(BothParsingTest):
    def test_gets_all_tRNA(self):
        data = self.importer.data(self.filename)
        rna_types = Counter(r.ncrna_class for r in data)
        assert rna_types['tRNA'] == 71

    def test_does_not_get_tRNA_pseudogenes(self):
        data = self.importer.data(self.filename)
        rna_types = set(r.ncrna_class for r in data)
        assert 'tRNA_pseudogene' not in rna_types
<|endoftext|>