Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
syntax: glob
*.py[cod]

2 changes: 1 addition & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
]
LOCAL_APPS = [
'hhcodingtask.users.apps.UsersConfig',
# Your stuff: custom apps go here
'hhcodingtask.synthetic.apps.SyntheticConfig'
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
Expand Down
1 change: 1 addition & 0 deletions config/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"localhost",
"0.0.0.0",
"127.0.0.1",
"31.28.168.164"
]

# DATABASES
Expand Down
1 change: 1 addition & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
include("hhcodingtask.users.urls", namespace="users"),
),
url(r"^accounts/", include("allauth.urls")),
url(r"^synthetic/", include("hhcodingtask.synthetic.urls", namespace="synthetic")),
# Your stuff: custom urls includes go here
] + static(
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
Expand Down
97 changes: 97 additions & 0 deletions hhcodingtask/static/js/synthetic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
(function($) {

$.synthetic = function(options) {
if (typeof(options) == "undefined" || options == null) {
options = {};
};

var API = {
options: $.extend({
rpc_url: "",
onSuccess: function(data, status) {},
onFailure: function(data) {},
onProgress: function(evt) {},
errorElementID: ''
},
options),
get_json: function(callback, error_callback) {
$.ajax({
url: API.options.rpc_url,
cache: false,
dataType: 'json',
beforeSend: function(xhr) {
// set auth token here
},
success: function(data, status) {
if (callback) callback(data, status);
}
}).fail(function(jqxhr, textStatus, error) {
var err = textStatus + ', ' + error;
if (error != '') API.error_happened(err);
});
},
post_json: function(url, form_data, headers, onSuccess, onFailure, onProgress) {
// ``form_data`` -- instance of ``FormData``
$.ajax({
url: url,
type: 'POST',
cache: false,
dataType: 'json',
data: form_data,
processData: false,
contentType: false,
timeout: 480000,
headers: headers,
success: function(data, status, jqXHR) {
if (data && data.hasOwnProperty('error')) {
onFailure(NaN, NaN, data);
return;
}
onSuccess(data, status);
},
beforeSend: function(xhr) {
// set auth header here
// xhr.setRequestHeader("authorization", "Token ...");
},
xhr: function() {
var req = $.ajaxSettings.xhr();
if (req) {
if (typeof req.upload == "object") {
req.upload.addEventListener("progress", function(evt) {
onProgress(evt);
});
}
}
return req;
}
}).fail(function(xhr, status, msg) {
var e = msg;
try {
e = $.parseJSON(xhr.responseText);
} catch (e) {};
onFailure(xhr, status, e);
});
},
error_happened: function(msg) {
var message = "Error loading content.";
if (msg != undefined) {
message = msg;
}
$('#' + API.options.errorElementID).empty().append('<span class="err">' + message + '</span>');
},
get_objects_list: function() {
API.get_json(API.options.onSuccess, API.options.onFailure);
},
create_object: function(city, temperature, callback){
var data = JSON.stringify({'city': city, 'temperature': temperature});
API.post_json(API.options.rpc_url, data, {}, function(data, status){
if(callback) callback(data);
}, function(xhr, status, msg){}, function(evt){});
}
};
return {
list: API.get_objects_list,
create: API.create_object
};
};
})(jQuery);
Empty file.
36 changes: 36 additions & 0 deletions hhcodingtask/synthetic/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from django import forms
from django.contrib import admin
from .models import GenericModel, AnyData


class GenericChangeForm(forms.ModelForm):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field, kind in AnyData().fields.items():
self.fields[field] = forms.CharField()
# TODO: add date type, populate with instance's data
if self.instance.pk:
pass

def clean(self):
try:
data_obj = AnyData(raw_data=self.cleaned_data)
data_obj.validate()
except (ModelValidationError, ModelConversionError) as exc:
result = {}
for field, messages in exc.messages.items():
raise forms.ValidationError(str(exc.messages[field].to_primitive()))
return self.cleaned_data

class Meta:
model = GenericModel
#fields = "__all__"
exclude = ['any_data']


@admin.register(GenericModel)
class GenericModelAdmin(admin.ModelAdmin):
form = GenericChangeForm

# TODO: define get_list_display, make fieldsets dynamic, etc
6 changes: 6 additions & 0 deletions hhcodingtask/synthetic/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class SyntheticConfig(AppConfig):
name = 'hhcodingtask.synthetic'
verbose_name = 'Synthethic'
Empty file.
27 changes: 27 additions & 0 deletions hhcodingtask/synthetic/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json

from django.db import models
from jsonfield import JSONField

from schematics.models import Model as SchematicsModel
from schematics.types import StringType, DecimalType, DateTimeType

from datetime import datetime


class AnyData(SchematicsModel):
city = StringType(required=True)
temperature = DecimalType(required=True)
taken_at = DateTimeType(default=datetime.now)


class GenericModel(models.Model):
any_data = JSONField()

def __unicode__(self):
return unicode(self.id)

def to_dict(self):
result = AnyData(raw_data=json.loads(self.any_data)).to_native()
result['id'] = self.pk
return result
3 changes: 3 additions & 0 deletions hhcodingtask/synthetic/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
Empty file.
101 changes: 101 additions & 0 deletions hhcodingtask/synthetic/tests/test_generic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import json

from django.test import TestCase, Client
from rest_framework import status

class TestCreateGenericObject(TestCase):

def setUp(self):
super().setUp()
self.client = Client()

def test_create_fails(self):
data = {'city': 'bad city'}
response = self.client.post('/synthetic/', content_type='application/json',
data=json.dumps(data))
assert response.status_code == status.HTTP_400_BAD_REQUEST

def test_create(self):
data = {'city': 'London', 'temperature': 18}
response = self.client.post('/synthetic/', content_type='application/json',
data=json.dumps(data))

assert response.status_code == status.HTTP_201_CREATED


class TestUpdateGenericObject(TestCase):

def setUp(self):
super().setUp()
self.client = Client()

data = {'city': 'London', 'temperature': 18}
response = self.client.post('/synthetic/', content_type='application/json',
data=json.dumps(data))

self.obj = response.json()

def test_update_succeeds(self):
pk = self.obj['id']
data = {'city': 'London', 'temperature': 18}
response = self.client.patch('/synthetic/detail/{}/'.format(pk),
content_type='application/json', data=json.dumps(data))

assert response.status_code == status.HTTP_202_ACCEPTED

def test_update_fails(self):
data = {'city': 'nowhere'}

pk = self.obj['id']
response = self.client.patch('/synthetic/detail/{}/'.format(pk),
content_type='application/json', data=json.dumps(data))

#import rpdb;rpdb.set_trace()
assert response.status_code == status.HTTP_400_BAD_REQUEST


class TestListGenericModel(TestCase):

def setUp(self):
super().setUp()
self.client = Client()

data = {'city': 'London', 'temperature': 18}
response = self.client.post('/synthetic/', content_type='application/json',
data=json.dumps(data))

self.obj = response.json()

def test_list(self):
response = self.client.get('/synthetic/')
obj = response.json()

assert response.status_code == status.HTTP_200_OK
assert int(obj['total'] == 1)

for pk in obj['items'].keys():
assert pk == '1'
item = obj['items'][pk]
assert item['city']
assert item['temperature']
assert item['taken_at']


class TestDeleteGenericObject(TestCase):

def setUp(self):
super().setUp()
self.client = Client()
self.data = {'city': 'London', 'temperature': 18}

response = self.client.post('/synthetic/', content_type='application/json',
data=json.dumps(self.data))
self.obj = response.json()

def test_delete(self):
response = self.client.delete('/synthetic/detail/{}/'.format(self.obj['id']))
assert response.status_code == status.HTTP_204_NO_CONTENT

response = self.client.get('/synthetic/')
obj = response.json()
assert self.obj['id'] not in obj
13 changes: 13 additions & 0 deletions hhcodingtask/synthetic/tests/test_urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.urls import reverse, resolve

from test_plus.test import TestCase


class TestSyntheticURLs(TestCase):
"""Test URL patterns for users app."""

def test_list_reverse(self):
self.assertEqual(reverse("synthetic:list"), "/synthetic/")

def test_update_reverse(self):
self.assertEqual(reverse("synthetic:detail", kwargs={'pk': 8}), "/synthetic/detail/8/")
13 changes: 13 additions & 0 deletions hhcodingtask/synthetic/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.conf.urls import url
from django.urls import path

from . import views

app_name = 'synthetic'

urlpatterns = [
url(regex=r"^$", view=views.GenericView.as_view(), name="list"),
url(regex=r"^form/", view=views.GenericFormView.as_view()),
url(regex=r"^detail/(?P<pk>\d+)/$", view=views.GenericDetailView.as_view(),
name="detail"),
]
Loading