diff --git a/server/social_network/models.py b/server/social_network/models.py index 8b36771..f5e669e 100644 --- a/server/social_network/models.py +++ b/server/social_network/models.py @@ -11,8 +11,13 @@ class Profile(models.Model): Django default User model is used for these purposes. """ + # Django provides a User class built-in, this makes it easy to do things like authentication and + # authorization, so rather than creat our own User model to replace Django's default one, we add + # a one-to-one relationship with a Django User so that we can associate our own data with it user = models.OneToOneField(User, on_delete=models.CASCADE) + created = models.DateTimeField(auto_now_add=True) + username = models.CharField(max_length=200) picture_url = models.URLField() @@ -32,7 +37,10 @@ class Post(models.Model): # all of a Profile instance's Posts can be accessed as `profile.posts` owner = models.ForeignKey(Profile, related_name='posts', on_delete=models.CASCADE) + # we store when the Post was created so that we can sort them by date in the UI created = models.DateTimeField(auto_now_add=True) + + # the actual text content of the Post title = models.CharField(max_length=200) def __str__(self): @@ -43,7 +51,9 @@ def __str__(self): class Vote(models.Model): """Vote maps a Profile upvoting a Post.""" + # Reference to the Profile that "owns" this Vote profile = models.ForeignKey(Profile, on_delete=models.CASCADE) + # Reference to the Post that this Vote belongs to post = models.ForeignKey(Post, on_delete=models.CASCADE) def __str__(self): diff --git a/server/social_network/serializers.py b/server/social_network/serializers.py index 0bc7757..e9d9054 100644 --- a/server/social_network/serializers.py +++ b/server/social_network/serializers.py @@ -6,42 +6,22 @@ from rest_framework import serializers -from .models import Post, Profile +from .models import Post -class PostSerializer(serializers.HyperlinkedModelSerializer): - """Serializes Posts. +class PostSerializer(serializers.Serializer): + """Serialize a Post to/from the database.""" - This is an example of a semi-customized Serializer class, for simple Models, the ModelSerializer - is capable of doing just about everything we want, but in this case we want to have a "computed" - field that counts how many votes a post has received. - """ + title = serializers.CharField(max_length=200) + created = serializers.DateTimeField() - class Meta: - """Model/fields for ModelSerializer.""" + def create(self, validated_data): + """Create and return a new `Post` instance, given the validated data.""" + return Post.objects.create(**validated_data) - model = Post - fields = ['id', 'url', 'created', 'owner', 'owner_name', 'title', 'vote_count'] - - vote_count = serializers.SerializerMethodField() - owner = serializers.HyperlinkedRelatedField(view_name='profile-detail', read_only=True) - owner_name = serializers.ReadOnlyField(source='owner.username') - - def get_vote_count(self, post): - """Count how many votes the `Post` has received.""" - return post.vote_set.count() - - -class ProfileSerializer(serializers.HyperlinkedModelSerializer): - """Serializes user Profiles.""" - - class Meta: - """Model/fields for ModelSerializer.""" - - model = Profile - fields = ['id', 'url', 'username', 'posts'] - - # this is a Django backwards relation mapping Profile -> Posts, so it won't be included by - # default by the ModelSerializer, so we have to add it manually - # Use the PostSerializer so that the Post objects will be fully instantiated - posts = PostSerializer(many=True, read_only=True) + def update(self, instance, validated_data): + """Update and return an existing `Post` instance, given the validated data.""" + instance.title = validated_data.get('title', instance.title) + instance.created = validated_data.get('created', instance.style) + instance.save() + return instance diff --git a/server/social_network/urls.py b/server/social_network/urls.py index 585eb6c..5928aee 100644 --- a/server/social_network/urls.py +++ b/server/social_network/urls.py @@ -1,20 +1,10 @@ """URLs specific to the social_network app.""" from django.conf.urls import include, url -from rest_framework.routers import DefaultRouter -from rest_framework.schemas import get_schema_view from . import views -router = DefaultRouter(trailing_slash=False) -router.register(r'profiles', views.ProfileViewSet) -router.register(r'posts', views.PostViewSet) - -# This generates a coreapi schema that can be used by coreapi clients -schema_view = get_schema_view(title='social_network API') - urlpatterns = [ - url(r'^', include(router.urls)), - url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), - url(r'^schema/$', schema_view), + url(r'^posts/?$', views.post_list), + url(r'^posts/(?P[0-9]+)/$', views.post_detail), ] diff --git a/server/social_network/views.py b/server/social_network/views.py index c07c10a..c1f6631 100644 --- a/server/social_network/views.py +++ b/server/social_network/views.py @@ -1,65 +1,51 @@ """API views for social_network.""" -from rest_framework import viewsets -from rest_framework.decorators import api_view, detail_route -from rest_framework.response import Response -from rest_framework.reverse import reverse - -from .models import Profile, Post, Vote -from .serializers import ProfileSerializer, PostSerializer - - -@api_view(['GET']) -def api_root(request, format=None): - """Root of API, this is useful for documentation generated by DRF.""" - return Response({ - 'profiles': reverse('profile-list', request=request, format=format), - 'posts': reverse('post-list', request=request, format=format) - }) - - -class ProfileViewSet(viewsets.ReadOnlyModelViewSet): - """This provides get and list functionality for Profiles.""" - - queryset = Profile.objects.all() - serializer_class = ProfileSerializer - - -class PostViewSet(viewsets.ModelViewSet): - """Get or create Posts. - - retrieve: - Return a post given its ID. - - list: - Get a paginated list of all Posts. - - create: - Create a new Post as the logged-in user. - """ - - queryset = Post.objects.all().order_by('-created') - serializer_class = PostSerializer - - def perform_create(self, serializer): - """Create a Post associated with the logged-in user.""" - serializer.save(owner=self.request.user.profile) - - @detail_route(methods=['POST', 'DELETE'], url_path='vote') - def vote(self, request, pk=None): - """Vote or unvote on a post.""" - post = self.get_object() - if request.method == 'POST': - # check if the vote already exists, if so don't allow the user to vote again - if Vote.objects.filter(profile=self.request.user.profile, post=post).exists(): - # the user already voted, just return the post directly - data = PostSerializer(post, context={'request': self.request}).data - return Response(data) - new_vote = Vote(profile=self.request.user.profile, post=post) - new_vote.save() - - elif request.method == 'DELETE': - Vote.objects.filter(profile=self.request.user.profile, post=post).delete() - - data = PostSerializer(post, context={'request': self.request}).data - return Response(data) +from django.http import HttpResponse, JsonResponse +from django.views.decorators.csrf import csrf_exempt + +from .models import Post +from .serializers import PostSerializer + + +# we're simplifying things by using csrf_exempt, in a real application we would want the security +# that it provides +@csrf_exempt +def post_list(request): + """List all posts, or create a new post.""" + if request.method == 'GET': + posts = Post.objects.all() + serializer = PostSerializer(posts, many=True) + return JsonResponse(serializer.data, safe=False) + + elif request.method == 'POST': + data = JSONParser().parse(request) + serializer = PostSerializer(data=data) + if serializer.is_valid(): + serializer.save() + return JsonResponse(serializer.data, status=201) + return JsonResponse(serializer.errors, status=400) + + +@csrf_exempt +def post_detail(request, pk): + """Retrieve, update or delete an individual Post.""" + try: + post = Post.objects.get(pk=pk) + except Post.DoesNotExist: + return HttpResponse(status=404) + + if request.method == 'GET': + serializer = PostSerializer(post) + return JsonResponse(serializer.data) + + elif request.method == 'PUT': + data = JSONParser().parse(request) + serializer = PostSerializer(post, data=data) + if serializer.is_valid(): + serializer.save() + return JsonResponse(serializer.data) + return JsonResponse(serializer.errors, status=400) + + elif request.method == 'DELETE': + post.delete() + return HttpResponse(status=204)