Code and Wine
Tags: django, python, djandorestframework, api
Published: 2019-07-25 09:37:05

#Introduction
Hello everyone, this is my first post and in it I want to share a cool thing.
This is not a guide or manual, (I never knew how to write them) this is a concept of an idea that I liked and it will be cool if it inspires someone =)
So ... I love wine. You probably know how easy it is to get lost in the variety of varieties, blends, producing countries with a lot of regions inside each, year of harvest and so on, the taste of the drink depends on all these and many other parameters. When I like wine, I I make notes about him, so as not to forget.
It would be cool if these notes were always at hand, how to do it? Of course, write an application =)
In addition to wine, I also love the django and django-rest-framework with their help, and we will implement the idea.

#Task
The application is a system of notes, where each user can create, modify and delete those of which is the author. We do not know yet whether it will be a web site or a mobile client. The database provider does not play a role because it is only a concept (I I chose postgres) As an additional functionality, registration through social networks is provided and sharing of your notes through them is the same. In principle, everything is simple.

#Implementation
full code available on github repo:
https://github.com/sixth-fox/vinecode

my requirements.txt:
Django==2.2.3
djangorestframework==3.10.1
psycopg2==2.8.3
python-decouple==3.1
pytz==2019.1
sqlparse==0.3.0

By the way I am used to writing code from model to routers, i know that some hold the opposite opinion.
I decided to make the structure like a tasting note, I had to go through a lot of options and in the end I stopped at
```python
class Note(models.Model):
    PERFECTLY = 5
    GOOD = 4
    SATISFACTORILY = 3
    BAD = 2
    ABOMINATION = 1
    SCORE_CHOICES = (
        (5, 'Excellent'),
        (4, 'Good'),
        (3, 'Satisfactory'),
        (2, 'Bad'),
        (1, 'Vinegar'),
    )
    date_create = models.DateTimeField(default=timezone.now)
    owner = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='notes')
    slug = models.SlugField(max_length=150, unique_for_date='date_create', default='')
    date = models.DateField()
    wine = models.CharField(max_length=150)
    region = models.CharField(max_length=150)
    variety = models.CharField(max_length=150)
    country = models.CharField(max_length=150)
    vintage = models.DateField()
    alcohol = models.FloatField()
    price = models.PositiveSmallIntegerField()
    appearance = models.CharField(max_length=150)
    aromas = models.CharField(max_length=150)
    flavors = models.CharField(max_length=150)
    score = models.SmallIntegerField(choices=SCORE_CHOICES)
```

As you can see, I have a basic user model, as long as this is enough.
Since we have nested models, override the create and update methods in the serializer.

```python
from django.contrib.auth.models import User
from rest_framework import serializers

from .models import Note


class UserSerializer(serializers.HyperlinkedModelSerializer):
    notes = serializers.HyperlinkedRelatedField(many=True,
                                                view_name='notes:note-detail',
                                                read_only=True)
    password = serializers.CharField(write_only=True)

    def create(self, validated_data):
        user = User(
            username=validated_data.get('username', None)
        )
        user.set_password(validated_data.get('password', None))
        user.save()
        return user

    def update(self, instance, validated_data):
        for field in validated_data:
            if field == 'password':
                instance.set_password(validated_data.get(field))
            else:
                instance.__setattr__(field, validated_data.get(field))
        instance.save()
        return instance

    class Meta:
        model = User
        fields = ('id', 'username', 'email', 'notes', 'password')
        extra_kwargs = {
            'url': {
                'view_name': 'notes:user-detail',
            }
        }


class NoteSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Note
        exclude = ('date_create',)
        extra_kwargs = {
            'url': {
                'view_name': 'notes:note-detail',
            }
}
```

The views is implemented on the inheritance of generics, the most controversial point is that I did not use routers, it is certainly not quite pythonic, sorry

```python
notes/views.py

@api_view(['GET'])
def root_api(request, format=None):
    return Response({
        'users': reverse('notes:user-list', request=request, format=format),
        'notes': reverse('notes:note-list', request=request, format=format),
    })


class NoteList(generics.ListCreateAPIView):
    queryset = Note.objects.all()
    serializer_class = NoteSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)

    def perform_create(self, serializer):
serializer.save(owner=self.request.user)


#more methods on github
```
Add custom permissions in permissions.py

```python
from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method == permissions.SAFE_METHODS:
            return True
return obj.owner == request.user
```
finally urls.py

```python
notes/urls.py

from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns

from . import views

app_name = 'notes'

urlpatterns = [
    path('', views.root_api, name='api-root'),
    path('note_list/', views.NoteList.as_view(), name='note-list'),
    path('note_detail/<int:pk>/', views.NoteDetail.as_view(), name='note-detail'),
    path('user_list', views.UserList.as_view(), name='user-list'),
    path('user_detail/<int:pk>/', views.UserDetail.as_view(), name='user-detail'),
]

urlpatterns = format_suffix_patterns(urlpatterns)
```