Skip to content

Commit

Permalink
[WIP]Due Dates: implement basic backend
Browse files Browse the repository at this point in the history
This commit implements a basic model definition for due dates with validation,
along with an API endpoint to create and delete these.
  • Loading branch information
julen committed Sep 14, 2016
1 parent 6850675 commit 6562257
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 0 deletions.
Empty file.
20 changes: 20 additions & 0 deletions pootle/apps/duedates/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.


from pootle.core.views.api import APIView

from .forms import DueDateForm
from .models import DueDate


class DueDateView(APIView):
model = DueDate
restrict_to_methods = ('POST', 'DELETE', )
fields = ('due_date', 'modified_by', 'pootle_path', )
add_form_class = DueDateForm
22 changes: 22 additions & 0 deletions pootle/apps/duedates/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from django import forms

from pootle.core.fields import ISODateTimeField

from .models import DueDate


class DueDateForm(forms.ModelForm):

due_date = ISODateTimeField()

class Meta:
model = DueDate
fields = ('due_date', 'pootle_path', 'modified_by', )
26 changes: 26 additions & 0 deletions pootle/apps/duedates/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
from django.conf import settings
import duedates.models


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='DueDate',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('due_date', models.DateTimeField()),
('pootle_path', models.CharField(db_index=True, max_length=255, validators=[duedates.models.validate_pootle_path])),
('modified_on', models.DateTimeField(auto_now_add=True)),
('modified_by', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file.
32 changes: 32 additions & 0 deletions pootle/apps/duedates/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models

INVALID_POOTLE_PATHS = ['/', '/projects/']


def validate_pootle_path(value):
if value in INVALID_POOTLE_PATHS:
raise ValidationError('Cannot set due date for this path.')


class DueDate(models.Model):

due_date = models.DateTimeField()
pootle_path = models.CharField(max_length=255, db_index=True,
validators=[validate_pootle_path])
modified_by = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True)
modified_on = models.DateTimeField(auto_now_add=True)

def save(self, *args, **kwargs):
self.full_clean()

super(DueDate, self).save(*args, **kwargs)
18 changes: 18 additions & 0 deletions pootle/apps/duedates/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from django.conf.urls import url

from .api import DueDateView


urlpatterns = [
url(r'^duedates/?$',
DueDateView.as_view(),
name='pootle-xhr-duedates'),
]
1 change: 1 addition & 0 deletions pootle/settings/50-project.conf
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ INSTALLED_APPS = [
# Pootle-specific
'accounts',
'contact',
'duedates',
'import_export', # Put before 'pootle' to ensure overextends works.
'pootle',
'pootle.core',
Expand Down
1 change: 1 addition & 0 deletions pootle/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
url(r'', include('pootle_profile.urls')),

# Pootle URLs
url(r'', include('duedates.urls')),
url(r'', include('staticpages.urls')),
url(r'^help/quality-checks/',
TemplateView.as_view(template_name="help/quality_checks.html"),
Expand Down
6 changes: 6 additions & 0 deletions pytest_pootle/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,9 @@ class SuggestionFactory(factory.django.DjangoModelFactory):

class Meta(object):
model = 'pootle_store.Suggestion'


class DueDateFactory(factory.django.DjangoModelFactory):

class Meta(object):
model = 'duedates.DueDate'
49 changes: 49 additions & 0 deletions tests/forms/duedate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

import pytest

from django.utils import timezone

from pytest_pootle.factories import UserFactory

from duedates.forms import DueDateForm
from duedates.models import INVALID_POOTLE_PATHS


@pytest.mark.parametrize('pootle_path', INVALID_POOTLE_PATHS)
def test_duedate_form_validation_invalid_paths(pootle_path):
"""Tests certain path restrictions when validating due date forms."""
form_data = {
'pootle_path': pootle_path,
}
form = DueDateForm(form_data)
assert not form.is_valid()

assert 'pootle_path' in form.errors
assert 'Cannot set due date for this path.' in form.errors['pootle_path']


@pytest.mark.django_db
@pytest.mark.parametrize('due_in', [timezone.now(), '2016-09-06T14:19:52.985Z'])
@pytest.mark.parametrize('pootle_path', [
'/ru/', '/ru/foo/', '/ru/foo/bar/', '/ru/foo/bar/baz.po',
'/projects/foo/', '/projects/foo/bar/', '/projects/foo/bar/baz.po'
])
def test_duedate_create(due_in, pootle_path):
"""Tests form validation for a set of paths and date time formats."""
user = UserFactory.create()

form_data = {
'due_date': due_in,
'modified_by': user.id,
'pootle_path': pootle_path,
}
form = DueDateForm(form_data)
assert form.is_valid()
# TODO: form.save
46 changes: 46 additions & 0 deletions tests/models/duedate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

import pytest

from django.core.exceptions import ValidationError
from django.utils import timezone

from pytest_pootle.factories import UserFactory

from duedates.models import INVALID_POOTLE_PATHS, DueDate


@pytest.mark.django_db
@pytest.mark.parametrize('pootle_path', INVALID_POOTLE_PATHS)
def test_duedate_create_invalid_paths(pootle_path):
"""Tests certain path restrictions when creating due dates."""
with pytest.raises(ValidationError) as excinfo:
DueDate.objects.create(pootle_path=pootle_path)

message_dict = excinfo.value.message_dict
assert 'pootle_path' in message_dict
assert 'Cannot set due date for this path.' in message_dict['pootle_path']


@pytest.mark.django_db
@pytest.mark.parametrize('pootle_path', [
'/ru/', '/ru/foo/', '/ru/foo/bar/', '/ru/foo/bar/baz.po',
'/projects/foo/', '/projects/foo/bar/', '/projects/foo/bar/baz.po'
])
def test_duedate_create(pootle_path):
"""Tests due dates creation for valid paths."""
user = UserFactory.create()
due_in = timezone.now()

due_date = DueDate.objects.create(
due_date=due_in,
modified_by=user,
pootle_path=pootle_path,
)
assert due_date.id is not None
80 changes: 80 additions & 0 deletions tests/views/duedate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

import pytest

from django.utils import timezone

from pytest_pootle.factories import DueDateFactory, UserFactory
from pytest_pootle.utils import create_api_request

from pootle.core.utils.json import jsonify

from duedates.models import DueDate
from duedates.api import DueDateView


def test_duedate_get(rf):
"""Tests the due date endpoint cannot be GET'ed."""
view = DueDateView.as_view()
request = create_api_request(rf, url='/')
response = view(request)
assert response.status_code == 405


@pytest.mark.django_db
def test_duedate_post(rf):
"""Tests due dates can be added."""
view = DueDateView.as_view()
user = UserFactory.create()
due_in = timezone.now()
pootle_path = '/ru/foo/bar/'

request_data = {
'due_date': due_in,
'modified_by': user.id,
'pootle_path': pootle_path,
}
request = create_api_request(rf, method='POST', data=request_data)
response = view(request)
assert response.status_code == 200

due_date = DueDate.objects.latest('id')

# Not checking `datetime` objects directly because microseconds are adjusted
# when serializing, so checking the serialized values.
assert jsonify(due_date.due_date) == jsonify(due_in)
assert due_date.modified_by == user
assert due_date.pootle_path == pootle_path


@pytest.mark.django_db
def test_duedate_delete(rf):
"""Tests due dates can be deleted."""
view = DueDateView.as_view()
user = UserFactory.create()
due_in = timezone.now()
pootle_path = '/ru/foo/bar/'

data = {
'due_date': due_in,
'modified_by': user,
'pootle_path': pootle_path,
}
due_date = DueDateFactory.create(**data)

assert due_date.id is not None
due_date_count_pre_delete = DueDate.objects.count()

request = create_api_request(rf, method='DELETE')
view_kwargs = {
'id': due_date.id,
}
response = view(request, **view_kwargs)
assert response.status_code == 200
assert DueDate.objects.count() == due_date_count_pre_delete - 1

0 comments on commit 6562257

Please sign in to comment.