Browse Source

First commit and most of the app is ready

tags/3-hours
Alireza Savand 1 year ago
commit
fd35e033b6

+ 9
- 0
.editorconfig View File

@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

+ 54
- 0
.gitignore View File

@@ -0,0 +1,54 @@
## IDE specific
# And yeah, you are using some kind of IDE around.
*.idea
.project
.pydevproject
.ropeproject/
.vscode/

## file
# Python compiled files.
*.pyc
# GetText compiled files.
*.mo
# Okay, SQLITE db, In dev env of course.
*.db
db.sqlite*
# And yeah this is env. HeHe!
.env
# you know this file is somehow suspicious, i don't know!
settings_local.py
# Backup files, mostly with unix editors.
*~
# Sometimes in dev env, I've got to use dump solution.
dump.json

## dir
# FileSystemCacheBasedBackend.
cache/
# Build will cum to your face where packaging is involved.
build/
# I remember i was young and keeps web-server logs in such a dir.
logs/
# And what da fuck is src/ doing here? I don't know.
src/
# you know media/ dir is where django keeps user-uploaded shit.
media/
# So daddy wanna know about code coverage.
.coverage
# Django dev trunk folder/repo dir
django-trunk/
# archive
archive/
.elasticbeanstalk/
# celery schedulers database file (shelve)
celerybeat-schedule
# Ignore Sphinx documentation
docs/
bower_components/
node_modules/
.static/
coverage_html/
media_test/
.translations/
staticfiles/

+ 0
- 0
api/__init__.py View File


+ 5
- 0
api/apps.py View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig


class ApiConfig(AppConfig):
name = 'api'

+ 0
- 0
api/migrations/__init__.py View File


+ 3
- 0
api/models.py View File

@@ -0,0 +1,3 @@
from django.db import models

# Create your models here.

+ 19
- 0
api/serializers.py View File

@@ -0,0 +1,19 @@
from typing import Tuple

from rest_framework import serializers

from livestock.models import Animal, AnimalWeight


class AnimalWeightSerializer(serializers.ModelSerializer):
class Meta:
model = AnimalWeight
fields = '__all__'


class AnimalSerializer(serializers.ModelSerializer):
weights = AnimalWeightSerializer(many=True, read_only=True)
class Meta:
model = Animal
fields: Tuple[str] = ('id', 'weights', )

+ 0
- 0
api/tests/__init__.py View File


+ 51
- 0
api/tests/test_api_views.py View File

@@ -0,0 +1,51 @@
from datetime import datetime

from django.urls import reverse
from django.utils import timezone
from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APITestCase

from api.serializers import AnimalSerializer
from livestock.models import Animal, AnimalWeight


class TestAPIViews(APITestCase):
"""Unit testing API Views."""
def test_add_animal(self) -> None:
resp: Response = self.client.post(path=reverse('api:animal-list'), data={'id': 100})
self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
self.assertEqual(Animal.objects.filter().count(), 1)
self.assertEqual(Animal.objects.first().id, 100)
def test_animal_weight_record(self) -> None:
animal: Animal = Animal.objects.create(id=100)
today = datetime.now()
resp: Response = self.client.post(
path=reverse('api:animal-add-weight', args=[animal.id, ]),
data={'weight': 50, 'weight_date': today}
)

self.assertEqual(resp.status_code, status.HTTP_201_CREATED)
self.assertEqual(AnimalWeight.objects.filter().count(), 1)
self.assertEqual(
resp.data,
{'id': 1, 'weight': 50.0, 'weight_date': f'{today.isoformat()}Z', 'animal': animal.id}
)
def test_animal_list(self) -> None:
today = timezone.now()
animal: Animal = Animal.objects.create(id=100)
animal.weights.create(weight=50, weight_date=today)
animal.weights.create(weight=52.1, weight_date=today)

resp: Response = self.client.get(path=reverse('api:animal-list'))
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertIsInstance(resp.data, list)
self.assertEqual(len(resp.data), 1)
self.assertEqual(resp.data[0], AnimalSerializer(instance=animal).data)

+ 9
- 0
api/urls.py View File

@@ -0,0 +1,9 @@
from rest_framework.routers import DefaultRouter

from api import views

app_name: str = 'api'

router = DefaultRouter()
router.register(r'animal', views.AnimalViewSet, base_name='animal')
urlpatterns = router.urls

+ 32
- 0
api/views.py View File

@@ -0,0 +1,32 @@
import copy
from django.core.exceptions import ValidationError
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response

from api.serializers import AnimalSerializer, AnimalWeightSerializer
from livestock.models import Animal


class AnimalViewSet(viewsets.ModelViewSet):
"""Animal View Set."""
serializer_class = AnimalSerializer
queryset = Animal.objects.filter()

@action(detail=True, methods=['post'], serializer_class=AnimalWeightSerializer, url_path='weight')
def add_weight(self, request, *args, **kwargs) -> Response:
"""Add weight record for an animal."""
data = copy.deepcopy(request.data)
data['animal'] = self.get_object().id
serializer = AnimalWeightSerializer(data=data)
if not serializer.is_valid():
raise ValidationError(serializer.errors)
serializer.save()

return Response(
data=AnimalWeightSerializer(instance=serializer.instance).data,
status=status.HTTP_201_CREATED
)

+ 7
- 0
flake8.ini View File

@@ -0,0 +1,7 @@
[flake8]
ignore = E701,F401,W503
max-line-length = 120
exclude =
venv,
migrations,
pmap/settings.py

+ 0
- 0
livestock/__init__.py View File


+ 3
- 0
livestock/admin.py View File

@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.

+ 5
- 0
livestock/apps.py View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig


class LivestockConfig(AppConfig):
name = 'livestock'

+ 38
- 0
livestock/migrations/0001_initial.py View File

@@ -0,0 +1,38 @@
# Generated by Django 2.0.5 on 2018-05-23 09:54

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Animal',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False, verbose_name='id')),
],
options={
'verbose_name': 'Animal',
'verbose_name_plural': 'Animals',
},
),
migrations.CreateModel(
name='AnimalWeight',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('weight', models.FloatField(verbose_name='weight')),
('weight_date', models.DateTimeField(verbose_name='weight date')),
('animal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='weights', to='livestock.Animal', verbose_name='animal')),
],
options={
'verbose_name': 'Animal Weight',
'verbose_name_plural': 'Animal Weights',
},
),
]

+ 0
- 0
livestock/migrations/__init__.py View File


+ 28
- 0
livestock/models.py View File

@@ -0,0 +1,28 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _


class Animal(models.Model):
"""Animal model."""
id = models.IntegerField(primary_key=True, verbose_name=_('id'))
class Meta:
verbose_name = _('Animal')
verbose_name_plural = _('Animals')
def __str__(self) -> str:
return f'Animal {self.id}'


class AnimalWeight(models.Model):
"""Animal Weight model."""
animal = models.ForeignKey(Animal, verbose_name=_('animal'), on_delete=models.CASCADE, related_name='weights')
weight = models.FloatField(verbose_name=_('weight'))
weight_date = models.DateTimeField(verbose_name=_('weight date'))
class Meta:
verbose_name = _('Animal Weight')
verbose_name_plural = _('Animal Weights')

def __str__(self) -> str:
return f'Animal {self.animal.id} W {self.weight} WDate {self.weight_date}'

+ 3
- 0
livestock/tests.py View File

@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.

+ 3
- 0
livestock/views.py View File

@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.

+ 15
- 0
manage.py View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pmap.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)

+ 0
- 0
pmap/__init__.py View File


+ 121
- 0
pmap/settings.py View File

@@ -0,0 +1,121 @@
"""
Django settings for pmap project.

Generated by 'django-admin startproject' using Django 2.0.5.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os
import dj_database_url
import environ

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'imasecret')
DEBUG = os.environ.get('DJANGO_DEBUG', '')

ALLOWED_HOSTS = [
'192.168.56.1',
'alireza-pmap.herokuapp.com',
'127.0.0.1',
]


# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'livestock',
'api',
'django_extensions',
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'pmap.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'pmap.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
os.environ.setdefault('DATABASE_URL', 'sqlite://')
DATABASES = {'default': dj_database_url.config()}

# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'

+ 20
- 0
pmap/urls.py View File

@@ -0,0 +1,20 @@
"""pmap URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path, include

urlpatterns = [
path('api/', include('api.urls', namespace='api')),
]

+ 16
- 0
pmap/wsgi.py View File

@@ -0,0 +1,16 @@
"""
WSGI config for pmap project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pmap.settings")

application = get_wsgi_application()

+ 5
- 0
requirements.txt View File

@@ -0,0 +1,5 @@
Django==2.0.5
djangorestframework==3.8.2
django-environ==0.4.4
dj-database-url==0.5.0
psycopg2-binary==2.7.4

+ 5
- 0
requirements/dev.txt View File

@@ -0,0 +1,5 @@
-r ../requirements.txt

coverage==4.4.2
ipdb==0.11
flake8==3.5.0

Loading…
Cancel
Save