diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..17413f9 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 0377a63..f6ffe5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ local_settings.py *.pyc .idea/ -declutter_db.sqlite3 \ No newline at end of file +declutter_db.sqlite3 +declutter_db.sqlite3.orig \ No newline at end of file diff --git a/README.md b/README.md index d3ec394..e6005ee 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # decluttter -App to declutter your life. +App to declutter your life and then stuff. diff --git a/appuser/migrations/0001_initial.py b/appuser/migrations/0001_initial.py index 74f082e..39cf509 100644 --- a/appuser/migrations/0001_initial.py +++ b/appuser/migrations/0001_initial.py @@ -9,6 +9,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('items', '0001_initial'), ] operations = [ @@ -16,8 +17,8 @@ class Migration(migrations.Migration): name='Follower', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('followee', models.IntegerField()), - ('follower', models.IntegerField()), + ('followee', models.ForeignKey(related_name='followee', to=settings.AUTH_USER_MODEL)), + ('follower', models.ForeignKey(related_name='follower', to=settings.AUTH_USER_MODEL)), ], options={ }, @@ -27,9 +28,10 @@ class Migration(migrations.Migration): name='Stream', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('item', models.TextField(max_length=120)), - ('created', models.DateField(auto_now_add=True)), + ('created', models.DateField(null=True, blank=True)), ('status', models.BooleanField(default=True)), + ('deleted_status', models.BooleanField(default=False)), + ('item', models.ForeignKey(related_name='item_id', to='items.Item')), ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), ], options={ diff --git a/appuser/migrations/0002_stream_deleted_status.py b/appuser/migrations/0002_stream_deleted_status.py deleted file mode 100644 index 594b915..0000000 --- a/appuser/migrations/0002_stream_deleted_status.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('appuser', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='stream', - name='deleted_status', - field=models.BooleanField(default=False), - preserve_default=True, - ), - ] diff --git a/appuser/models.py b/appuser/models.py index b9b58a1..604fe35 100644 --- a/appuser/models.py +++ b/appuser/models.py @@ -1,11 +1,12 @@ from django.db import models from django.contrib.auth.models import User +from items.models import Item # Create your models here. class Stream(models.Model): - item = models.TextField(max_length=120) - created = models.DateField(auto_now_add=True) + item = models.ForeignKey(Item, related_name='item_id') + created = models.DateField(null=True, blank=True) status = models.BooleanField(default=True) deleted_status = models.BooleanField(default=False) user = models.ForeignKey(User) @@ -15,8 +16,8 @@ def __unicode__(self): class Follower(models.Model): - followee = models.IntegerField() - follower = models.IntegerField() + followee = models.ForeignKey(User, related_name="followee") + follower = models.ForeignKey(User, related_name="follower") def __unicode__(self): return u"Followee: {}, Follower: {}".format(self.followee, self.follower) diff --git a/appuser/serializers.py b/appuser/serializers.py index f3069aa..dd61350 100644 --- a/appuser/serializers.py +++ b/appuser/serializers.py @@ -4,7 +4,7 @@ class StreamSerializer(serializers.ModelSerializer): class Meta: model = Stream - fields = ("id", "item", "created", "status", "deleted_status") + fields = ("id", "item", "created", "status", "deleted_status", "user") class FollowerSerializer(serializers.ModelSerializer): diff --git a/appuser/urls.py b/appuser/urls.py index b88232f..cd43cfc 100644 --- a/appuser/urls.py +++ b/appuser/urls.py @@ -1,13 +1,14 @@ from django.conf.urls import patterns, url from appuser.views import StreamListCreateAPIView, StreamRetrieveUpdateDestroyAPIView, FollowerCreateAPIView, \ - FollowerRetrieveUpdateDestroyAPIView, FollowerListAPIView + FollowerRetrieveUpdateDestroyAPIView, FollowerListAPIView, FollowerFriendsDetailListAPIView urlpatterns = patterns('', url(r'^list/$', StreamListCreateAPIView.as_view()), url(r'^update/(?P\w+)$', StreamRetrieveUpdateDestroyAPIView.as_view()), url(r'^create/follower/$', FollowerCreateAPIView.as_view()), url(r'list/follower/$', FollowerListAPIView.as_view()), - url(r'^update/follower/(?P.+)/(?P.+)/$', FollowerRetrieveUpdateDestroyAPIView.as_view()) + url(r'^update/follower/(?P.+)/(?P.+)/$', FollowerRetrieveUpdateDestroyAPIView.as_view()), + url(r'^detail/follower/(?P.+)/(?P.+)/$', FollowerFriendsDetailListAPIView.as_view()) ) __author__ = 'andy' diff --git a/appuser/views.py b/appuser/views.py index 69debd4..b70bae6 100644 --- a/appuser/views.py +++ b/appuser/views.py @@ -3,13 +3,18 @@ from appuser.models import Stream, Follower from rest_framework.generics import ListAPIView, CreateAPIView, ListCreateAPIView, RetrieveUpdateDestroyAPIView from appuser.serializers import StreamSerializer, FollowerSerializer +from items.api.serializers import UserSerialize from django.contrib.auth.models import User +import pdb class StreamListCreateAPIView(ListCreateAPIView): - queryset = Stream.objects.all() serializer_class = StreamSerializer + def get_queryset(self): + user = self.request.user + return Stream.objects.filter(user=user) + def post(self, request, *args, **kwargs): serializer = StreamSerializer(data=request.data) if serializer.is_valid(): @@ -43,7 +48,7 @@ def put(self, request, stream_id, format=None, *args, **kwargs): def patch(self, request, stream_id, format=None, *args, **kwargs): snippet = Stream.objects.get(pk=stream_id) - serializer = StreamSerializer(snippet, data=request.data) + serializer = StreamSerializer(snippet, data=request.data, partial=True) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) @@ -61,7 +66,18 @@ class FollowerCreateAPIView(CreateAPIView): serializer_class = FollowerSerializer def post(self, request, *args, **kwargs): - follower_id = User.objects.get(email=request.data['follower']) + try: + follower_id = User.objects.get(email=request.data['follower']) + + except: + return Response({"error": "This user does not exist"}, status=status.HTTP_404_NOT_FOUND) + + try: + if Follower.objects.get(followee=request.user.id, follower=follower_id.id): + return Response({"error": "You are already friends"}, status=status.HTTP_404_NOT_FOUND) + + except: + pass data = { 'followee': request.user.id, @@ -76,19 +92,24 @@ def post(self, request, *args, **kwargs): serializer = FollowerSerializer(data=data) serializer_reverse = FollowerSerializer(data=data_reverse) + if serializer.is_valid() and serializer_reverse.is_valid(): serializer.save() serializer_reverse.save() return Response(serializer.data, status=status.HTTP_200_OK) else: - return Response({"error": "This stream does not exist"}, status=status.HTTP_404_NOT_FOUND) + return Response({"error": "Please enter a valid email"}, status=status.HTTP_404_NOT_FOUND) class FollowerListAPIView(ListAPIView): - queryset = Follower.objects.all() + # queryset = Follower.objects.all() serializer_class = FollowerSerializer + def get_queryset(self): + user = self.request.user.id + return Follower.objects.filter(followee=user) + class FollowerRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView): serializer_class = FollowerSerializer @@ -130,4 +151,29 @@ def delete(self, request, followee_id, follower_id, *args, **kwargs): snippet.delete() snippet_reverse = Follower.objects.get(followee=follower_id, follower=followee_id) snippet_reverse.delete() - return Response(status=status.HTTP_204_NO_CONTENT) \ No newline at end of file + return Response(status=status.HTTP_204_NO_CONTENT) + +# Setting up authorization model for access by user to friends detail. +class FollowerFriendsDetailListAPIView(ListAPIView): + serializer_class = UserSerialize + + def get(self, request, user_id, follower_id, *args, **kwargs): + # pdb.set_trace() + try: + if request.user.id != int(user_id): + return Response({"error": "You are not authorized."}, status=status.HTTP_403_FORBIDDEN) + + except: + pass + + try: + if not Follower.objects.get(followee=user_id, follower=follower_id.id): + return Response({"error": "You are not friends"}, status=status.HTTP_403_FORBIDDEN) + + except: + pass + + queryset = User.objects.get(pk=follower_id) + serializer = UserSerialize(queryset) + + return Response(serializer.data, status=status.HTTP_200_OK) \ No newline at end of file diff --git a/authentication/__init__.py b/authentication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authentication/admin.py b/authentication/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/authentication/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/authentication/app_settings.py b/authentication/app_settings.py new file mode 100644 index 0000000..5aa6aa0 --- /dev/null +++ b/authentication/app_settings.py @@ -0,0 +1,42 @@ +from django.conf import settings + +from rest_auth.serializers import ( + TokenSerializer as DefaultTokenSerializer, + UserDetailsSerializer as DefaultUserDetailsSerializer, + LoginSerializer as DefaultLoginSerializer, + PasswordResetSerializer as DefaultPasswordResetSerializer, + PasswordResetConfirmSerializer as DefaultPasswordResetConfirmSerializer, + PasswordChangeSerializer as DefaultPasswordChangeSerializer) +from .utils import import_callable + + +serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {}) + +TokenSerializer = import_callable( + serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer)) + +UserDetailsSerializer = import_callable( + serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer) +) + +LoginSerializer = import_callable( + serializers.get('LOGIN_SERIALIZER', DefaultLoginSerializer) +) + +PasswordResetSerializer = import_callable( + serializers.get('PASSWORD_RESET_SERIALIZER', + DefaultPasswordResetSerializer) +) + +PasswordResetConfirmSerializer = import_callable( + serializers.get('PASSWORD_RESET_CONFIRM_SERIALIZER', + DefaultPasswordResetConfirmSerializer) +) + +PasswordChangeSerializer = import_callable( + serializers.get('PASSWORD_CHANGE_SERIALIZER', + DefaultPasswordChangeSerializer) +) + + +__author__ = 'andy' diff --git a/authentication/migrations/__init__.py b/authentication/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/authentication/models.py b/authentication/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/authentication/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/authentication/register/__init__.py b/authentication/register/__init__.py new file mode 100644 index 0000000..45a598f --- /dev/null +++ b/authentication/register/__init__.py @@ -0,0 +1 @@ +__author__ = 'andy' diff --git a/authentication/register/views.py b/authentication/register/views.py new file mode 100644 index 0000000..fd999d7 --- /dev/null +++ b/authentication/register/views.py @@ -0,0 +1,85 @@ +from django.http import HttpRequest +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.permissions import AllowAny +from rest_framework import status + +from allauth.account.views import SignupView, ConfirmEmailView +from allauth.account.utils import complete_signup +from allauth.account import app_settings + +from rest_auth.app_settings import UserDetailsSerializer +from rest_auth.registration.serializers import SocialLoginSerializer +from rest_auth.views import Login + + +class Register(APIView, SignupView): + + permission_classes = (AllowAny,) + user_serializer_class = UserDetailsSerializer + allowed_methods = ('POST', 'OPTIONS', 'HEAD') + + def get(self, *args, **kwargs): + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def put(self, *args, **kwargs): + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def form_valid(self, form): + self.user = form.save(self.request) + if isinstance(self.request, HttpRequest): + request = self.request + else: + request = self.request._request + return complete_signup(request, self.user, + app_settings.EMAIL_VERIFICATION, + self.get_success_url()) + + def post(self, request, *args, **kwargs): + self.initial = {} + self.request.POST = self.request.DATA.copy() + form_class = self.get_form_class() + self.form = self.get_form(form_class) + if self.form.is_valid(): + self.form_valid(self.form) + return self.get_response() + else: + return self.get_response_with_errors() + + def get_response(self): + serializer = self.user_serializer_class(instance=self.user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def get_response_with_errors(self): + return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST) + + +class VerifyEmail(APIView, ConfirmEmailView): + + permission_classes = (AllowAny,) + allowed_methods = ('POST', 'OPTIONS', 'HEAD') + + def get(self, *args, **kwargs): + return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED) + + def post(self, request, *args, **kwargs): + self.kwargs['key'] = self.request.DATA.get('key', '') + confirmation = self.get_object() + confirmation.confirm(self.request) + return Response({'message': 'ok'}, status=status.HTTP_200_OK) + + +class SocialLogin(Login): + """ + class used for social authentications + example usage for facebook + + from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter + class FacebookLogin(SocialLogin): + adapter_class = FacebookOAuth2Adapter + """ + + serializer_class = SocialLoginSerializer + + +__author__ = 'andy' diff --git a/authentication/tests.py b/authentication/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/authentication/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/authentication/urls.py b/authentication/urls.py new file mode 100644 index 0000000..947ae75 --- /dev/null +++ b/authentication/urls.py @@ -0,0 +1,20 @@ +from django.conf.urls import patterns, url +from django.contrib import admin +import appuser.urls as user_urls +from authentication.views import Facebooklogin, Login, Logout, PasswordChange, UserDetails +from register.views import Register +admin.autodiscover() + +urlpatterns = patterns('', + # Examples: + # url(r'^$', 'declutter.views.home', name='home'), + # url(r'^blog/', include('blog.urls')), + url(r'^facebook/$', Facebooklogin.as_view(), name='fb_login'), + url(r'^login/$', Login.as_view(), name='login'), + url(r'^logout/$', Logout.as_view(), name='logout'), + url(r'^register/$', Register.as_view(), name='register') +) + + + +__author__ = 'andy' diff --git a/authentication/utils.py b/authentication/utils.py new file mode 100644 index 0000000..8daa11d --- /dev/null +++ b/authentication/utils.py @@ -0,0 +1,13 @@ +from six import string_types +from django.utils.importlib import import_module + + +def import_callable(path_or_callable): + if hasattr(path_or_callable, '__call__'): + return path_or_callable + else: + assert isinstance(path_or_callable, string_types) + package, attr = path_or_callable.rsplit('.', 1) + return getattr(import_module(package), attr) + +__author__ = 'andy' diff --git a/authentication/views.py b/authentication/views.py new file mode 100644 index 0000000..7ec7087 --- /dev/null +++ b/authentication/views.py @@ -0,0 +1,169 @@ +from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter +from rest_auth.registration.views import SocialLogin + +from django.contrib.auth import login, logout +from django.conf import settings + +from rest_framework import status +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.generics import GenericAPIView +from rest_framework.permissions import IsAuthenticated, AllowAny +from rest_framework.authtoken.models import Token +from rest_framework.generics import RetrieveUpdateAPIView + +from .app_settings import (TokenSerializer, UserDetailsSerializer, + LoginSerializer, PasswordResetSerializer, PasswordResetConfirmSerializer, + PasswordChangeSerializer) + + +class Login(GenericAPIView): + + """ + Check the credentials and return the REST Token + if the credentials are valid and authenticated. + Calls Django Auth login method to register User ID + in Django session framework + + Accept the following POST parameters: username, password + Return the REST Framework Token Object's key. + """ + permission_classes = (AllowAny,) + serializer_class = LoginSerializer + token_model = Token + response_serializer = TokenSerializer + + def login(self): + # import ipdb; ipdb.set_trace() + self.user = self.serializer.validated_data['user'] + self.token, created = self.token_model.objects.get_or_create( + user=self.user) + if getattr(settings, 'REST_SESSION_LOGIN', True): + login(self.request, self.user) + + def get_response(self): + return Response(self.response_serializer(self.token).data, + status=status.HTTP_200_OK) + + def get_error_response(self): + return Response(self.serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + + def post(self, request, *args, **kwargs): + self.serializer = self.get_serializer(data=self.request.DATA) + if not self.serializer.is_valid(): + return self.get_error_response() + self.login() + return self.get_response() + + +class Logout(APIView): + + """ + Calls Django logout method and delete the Token object + assigned to the current User object. + + Accepts/Returns nothing. + """ + permission_classes = (AllowAny,) + + def post(self, request): + try: + request.user.auth_token.delete() + except: + pass + + logout(request) + + return Response({"success": "Successfully logged out."}, + status=status.HTTP_200_OK) + + +class UserDetails(RetrieveUpdateAPIView): + + """ + Returns User's details in JSON format. + + Accepts the following GET parameters: token + Accepts the following POST parameters: + Required: token + Optional: email, first_name, last_name and UserProfile fields + Returns the updated UserProfile and/or User object. + """ + serializer_class = UserDetailsSerializer + permission_classes = (IsAuthenticated,) + + def get_object(self): + return self.request.user + + +class PasswordReset(GenericAPIView): + + """ + Calls Django Auth PasswordResetForm save method. + + Accepts the following POST parameters: email + Returns the success/fail message. + """ + + serializer_class = PasswordResetSerializer + permission_classes = (AllowAny,) + + def post(self, request, *args, **kwargs): + # Create a serializer with request.DATA + serializer = self.get_serializer(data=request.DATA) + + if not serializer.is_valid(): + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + serializer.save() + # Return the success message with OK HTTP status + return Response({"success": "Password reset e-mail has been sent."}, + status=status.HTTP_200_OK) + + +class PasswordResetConfirm(GenericAPIView): + + """ + Password reset e-mail link is confirmed, therefore this resets the user's password. + + Accepts the following POST parameters: new_password1, new_password2 + Accepts the following Django URL arguments: token, uid + Returns the success/fail message. + """ + + serializer_class = PasswordResetConfirmSerializer + permission_classes = (AllowAny,) + + def post(self, request): + serializer = self.get_serializer(data=request.DATA) + if not serializer.is_valid(): + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + serializer.save() + return Response({"success": "Password has been reset with the new password."}) + + +class PasswordChange(GenericAPIView): + + """ + Calls Django Auth SetPasswordForm save method. + + Accepts the following POST parameters: new_password1, new_password2 + Returns the success/fail message. + """ + + serializer_class = PasswordChangeSerializer + permission_classes = (IsAuthenticated,) + + def post(self, request): + serializer = self.get_serializer(data=request.DATA) + if not serializer.is_valid(): + return Response(serializer.errors, + status=status.HTTP_400_BAD_REQUEST) + serializer.save() + return Response({"success": "New password has been saved."}) + +#Facebook +class Facebooklogin(SocialLogin): + adapter_class = FacebookOAuth2Adapter diff --git a/declutter/settings.py b/declutter/settings.py index c88e294..addf098 100644 --- a/declutter/settings.py +++ b/declutter/settings.py @@ -36,15 +36,24 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', + 'django.contrib.sites', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', + 'corsheaders', 'rest_framework', + 'rest_framework.authtoken', + 'allauth', + 'allauth.account', + 'rest_auth.registration', 'appuser', 'items', - 'debug_toolbar' + 'debug_toolbar', + 'rest_auth', + 'allauth.socialaccount', + 'allauth.socialaccount.providers.facebook' ) MIDDLEWARE_CLASSES = ( @@ -55,12 +64,27 @@ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware' ) ROOT_URLCONF = 'declutter.urls' WSGI_APPLICATION = 'declutter.wsgi.application' +#For Django-allauth Template Context Processor Settings +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.request", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.static", + "django.core.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "allauth.socialaccount.context_processors.socialaccount" +) + # Database # https://docs.djangoproject.com/en/1.7/ref/settings/#databases @@ -85,6 +109,31 @@ USE_TZ = True +#CORs settings +CORS_ORIGIN_ALLOW_ALL = True +CORS_ALLOW_CREDENTIALS = True + + +ALLOWED_HOSTS = ["localhost", "127.0.0.1"] +CORS_ALLOW_HEADERS = ( + 'x-requested-with', + 'content-type', + 'accept', + 'origin', + 'authorization', + 'X-CSRFToken', + 'Api-Authorization', +) + +CORS_ALLOW_METHODS = ( + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'OPTIONS' +) + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.7/howto/static-files/ @@ -103,6 +152,7 @@ ) } +SITE_ID = 1 #EMAIL settings EMAIL_USE_TLS = True diff --git a/declutter/urls.py b/declutter/urls.py index cd6ad8f..8b8c5bd 100644 --- a/declutter/urls.py +++ b/declutter/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import patterns, include, url from django.contrib import admin import appuser.urls as user_urls +from authentication.views import Facebooklogin admin.autodiscover() urlpatterns = patterns('', @@ -10,4 +11,7 @@ url(r'^api/appuser/', include(user_urls)), url(r'^admin/', include(admin.site.urls)), url(r'^api/items/', include('items.api.urls')), + url(r'^api/rest-auth/', include('authentication.urls')), + url(r'^api/rest-auth/registration/', include('rest_auth.registration.urls')), + url(r'^api/rest_auth/facebook/$', Facebooklogin.as_view(), name='fb_login') ) diff --git a/declutter_db.sqlite3 b/declutter_db.sqlite3 deleted file mode 100644 index fb56528..0000000 Binary files a/declutter_db.sqlite3 and /dev/null differ diff --git a/items/api/serializers.py b/items/api/serializers.py index 2d06f1f..f13cc9a 100644 --- a/items/api/serializers.py +++ b/items/api/serializers.py @@ -1,10 +1,23 @@ from rest_framework import serializers +from django.contrib.auth.models import User from items.models import Item +class UserSerialize(serializers.ModelSerializer): + + class Meta: + model = User + + class ItemSerializer(serializers.ModelSerializer): + poster = UserSerialize(read_only=True) + claimer = UserSerialize(read_only=True) + + class Meta: + model = Item + fields = ("id", "poster", "claimer", "item_name", "description", "created", "image", "availability", "category") +class SecondItemSerializer(serializers.ModelSerializer): class Meta: model = Item - fields = ("id", "poster_id", "claimer_id", "item_name", "description", "created", "image", "availability", "category") diff --git a/items/api/urls.py b/items/api/urls.py index aaefe35..a44499b 100644 --- a/items/api/urls.py +++ b/items/api/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import patterns, url -from views import ItemListAPIView, ItemDetailAPIView, ItemCreateAPIView, ItemGenericListAPIView, ItemGenericRetrieveUpdateAPIView +from views import ItemListAPIView, ItemDetailAPIView, ItemCreateAPIView, ItemGenericListAPIView, ItemGenericRetrieveUpdateAPIView, ItemListCreateAPIView, \ + ClaimItemListCreateAPIView, ClaimItemDetailAPIView, ClaimGenericRetrieveUpdateAPIView, ItemDetail2APIView, ItemUpdate urlpatterns = patterns('', url(r'^$', ItemListAPIView.as_view(), name='ItemListAPIView'), @@ -7,4 +8,10 @@ url(r'^create/$', ItemCreateAPIView.as_view()), url(r'^list/$', ItemGenericListAPIView.as_view()), url(r'^generics/id/(?P\d+)/$', ItemGenericRetrieveUpdateAPIView.as_view(), name="item-generic-single"), -) \ No newline at end of file + url(r'^poster/$', ItemListCreateAPIView.as_view()), + url(r'^claimer/$', ClaimItemListCreateAPIView.as_view()), + url(r'^claim/(?P\d+)/$', ClaimItemDetailAPIView.as_view()), + url(r'^mine/(?P\d+)/$', ClaimGenericRetrieveUpdateAPIView.as_view()), + url(r'^delete/(?P\d+)/$', ItemDetail2APIView.as_view()), + url(r'^update/(?P\d+)/$', ItemUpdate.as_view()) +) diff --git a/items/api/views.py b/items/api/views.py index 5a9cd6e..aaeb04b 100644 --- a/items/api/views.py +++ b/items/api/views.py @@ -1,8 +1,8 @@ from rest_framework.views import APIView, Response from rest_framework import status -from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveUpdateAPIView -from items.api.serializers import ItemSerializer -from items.models import Item +from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveUpdateAPIView, ListCreateAPIView +from items.api.serializers import ItemSerializer, SecondItemSerializer +from items.models import Item, User class ItemListAPIView(APIView): @@ -32,7 +32,7 @@ def put(self, request, item_id, *args): serializer = ItemSerializer(data=request.data) if serializer.is_valid(): - serializer.save() + serializer.save(poster=request.user) return Response(serializer.data, status.HTTP_202_ACCEPTED) else: @@ -46,7 +46,7 @@ def create(self, request, *args): serializer = ItemSerializer(data=request.data) if serializer.is_valid(): - serializer.save() + serializer.save(poster=request.user) return Response(serializer.data, status.HTTP_201_CREATED) else: @@ -67,4 +67,108 @@ class ItemGenericRetrieveUpdateAPIView(RetrieveUpdateAPIView): lookup_field = 'id' def get_queryset(self): - return Item.objects.all() \ No newline at end of file + return Item.objects.all() + + +class ItemListCreateAPIView(ListCreateAPIView): + serializer_class = ItemSerializer + + def get_queryset(self): + user = self.request.user + return Item.objects.filter(poster=user) + + +class ClaimItemListCreateAPIView(ListCreateAPIView): + serializer_class = ItemSerializer + + def get_queryset(self): + user = self.request.user + return Item.objects.filter(claimer=user) + + +class ClaimItemDetailAPIView(CreateAPIView): + serializer_class = ItemSerializer + + def put(self, request, *args, **kwargs): + serializer = ItemSerializer(data=request.data) + item = Item.objects.filter(pk=kwargs['item_id']) + user = self.request.user + # user = User.objects.get(username=request.DATA['user']) + item.update(claimer=user) + + if serializer.is_valid(): + serializer.save(claimer=request.user) + #can we add availability=false here? + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + + else: + return Response(serializer.errors, status.HTTP_400_BAD_REQUEST) + + +class ClaimGenericRetrieveUpdateAPIView(RetrieveUpdateAPIView): + model = Item + serializer_class = ItemSerializer + lookup_field = 'id' + + def get_queryset(self): + return Item.objects.all() + + +class ItemDetail2APIView(APIView): + def get(self, request, item_id): + item = Item.objects.get(pk=item_id) + serializer = ItemSerializer(item) + return Response(serializer.data, status=status.HTTP_200_OK) + + # def delete(self, request, item_id): + # item = Item.objects.get(pk=item_id) + # if item: + # item.delete() + # return Response(status=status.HTTP_204_NO_CONTENT) + # else: + # return Response(status=status.HTTP_400_BAD_REQUEST) + + def put(self, request): + serializer = ItemSerializer(data=request.data) + if serializer.is_valid(): + + serializer.delete() + return Response(serializer.data, status.HTTP_202_ACCEPTED) + + else: + return Response(serializer.errors, status.HTTP_400_BAD_REQUEST) + +class ItemUpdate(RetrieveUpdateAPIView): + serializer_class = SecondItemSerializer + + def get(self, request, item_id, format=None, *args, **kwargs): + try: + queryset = Item.objects.get(pk=item_id) + serializer = SecondItemSerializer(queryset) + + return Response(serializer.data, status=status.HTTP_200_OK) + + except: + return Response(status=status.HTTP_404_NOT_FOUND) + + + def put(self, request, item_id, format=None, *args, **kwargs): + snippet = Item.objects.get(pk=item_id) + serializer = SecondItemSerializer(snippet, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + else: + return Response({"error": "This item does not exist"}, status=status.HTTP_404_NOT_FOUND) + + def patch(self, request, item_id, format=None, *args, **kwargs): + snippet = Item.objects.get(pk=item_id) + serializer = SecondItemSerializer(snippet, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + + else: + return Response({"error": "This stream does not exist"}, status=status.HTTP_404_NOT_FOUND) + diff --git a/items/migrations/0001_initial.py b/items/migrations/0001_initial.py new file mode 100644 index 0000000..c6130f8 --- /dev/null +++ b/items/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('item_name', models.CharField(max_length=100)), + ('description', models.TextField(max_length=500)), + ('created', models.DateField(auto_now_add=True)), + ('image', models.CharField(max_length=100, null=True, blank=True)), + ('availability', models.BooleanField(default=True)), + ('category', models.CharField(max_length=50, choices=[(b'BOOKS', b'Books'), (b'ENTERTAINMENT', b'Movies, Music, and Games'), (b'ELECTRONICS', b'Electronics and Computers'), (b'HOME', b'Home'), (b'GARDEN', b'Garden and Tools'), (b'BEAUTY', b'Beauty and Health'), (b'TOYS', b'Toys, Kids and Baby'), (b'CLOTHING', b'Clothing, Shoes and Jewelry'), (b'SPORTS', b'Sports and Outdoors'), (b'AUTOMOTIVE', b'Automotive')])), + ('claimer', models.ForeignKey(related_name='claimed_items', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ('poster', models.ForeignKey(related_name='posted_items', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Item', + 'verbose_name_plural': 'Items', + }, + bases=(models.Model,), + ), + ] diff --git a/items/models.py b/items/models.py index a384dd2..3a2f01a 100644 --- a/items/models.py +++ b/items/models.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import User from django.db import models # Create your models here. @@ -20,8 +21,8 @@ class Meta: ('AUTOMOTIVE', 'Automotive'), ) - poster_id = models.IntegerField() - claimer_id = models.IntegerField() + poster = models.ForeignKey(User, related_name="posted_items") + claimer = models.ForeignKey(User, null=True, blank=True, related_name="claimed_items") item_name = models.CharField(max_length=100) description = models.TextField(max_length=500) created = models.DateField(auto_now_add=True) @@ -29,5 +30,6 @@ class Meta: availability = models.BooleanField(default=True) category = models.CharField(max_length=50, choices=CATEGORIES) - def __str__(self): - return "%s on %" % (self.item_name, self.created) + def __unicode__(self): + return "{} on {}".format(self.item_name, self.created) + diff --git a/requirements.txt b/requirements.txt index b75b658..df893fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,14 @@ Django==1.7.4 +django-allauth==0.19.1 +django-cors-headers==1.0.0 django-debug-toolbar==1.2.2 django-filter==0.9.2 +django-rest-auth==0.3.4 djangorestframework==3.0.5 Markdown==2.6 +oauthlib==0.7.2 +python-openid==2.2.5 +requests==2.5.3 +requests-oauthlib==0.4.2 +six==1.9.0 sqlparse==0.1.14 diff --git a/static/.tmp/styles/main.css b/static/.tmp/styles/main.css index af74efc..9ed87e4 100644 --- a/static/.tmp/styles/main.css +++ b/static/.tmp/styles/main.css @@ -80,6 +80,14 @@ body { .jumbotron { border-bottom: 0; } + + .other-color{ + background: #dbeaf0; + } + + .well{ + background-color: #dbeaf0; +} } -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0UsaUJBQWdCO0VBQ2hCLGtCQUFpQjtFQUNqQixhQUFZO0VBQ1osa0JBQWlCO0VBQ2xCOztBQUVEO0VBQ0UsWUFBVztFQUNaOztBQUVELDRFQUEyRTtBQUMzRTs7O0VBR0Usb0JBQW1CO0VBQ25CLHFCQUFvQjtFQUNyQjs7QUFFRCx5QkFBd0I7QUFDeEI7RUFDRSxrQ0FBaUM7RUFDakMscUJBQW9CO0VBQ3JCO0FBQ0Qsa0VBQWlFO0FBQ2pFO0VBQ0UsZUFBYztFQUNkLGtCQUFpQjtFQUNqQixtQkFBa0I7RUFDbEIsc0JBQXFCO0VBQ3RCOztBQUVELHlCQUF3QjtBQUN4QjtFQUNFLG1CQUFrQjtFQUNsQixhQUFZO0VBQ1osK0JBQThCO0VBQy9COztBQUVEO0VBQ0UsZ0JBQWU7RUFDaEI7O0FBRUQsZ0RBQStDO0FBQy9DO0VBQ0Usb0JBQW1CO0VBQ25CLGtDQUFpQztFQUNsQztBQUNEO0VBQ0UsaUJBQWdCO0VBQ2hCLG9CQUFtQjtFQUNwQjs7QUFFRCxtQ0FBa0M7QUFDbEM7RUFDRSxnQkFBZTtFQUNoQjtBQUNEO0VBQ0Usa0JBQWlCO0VBQ2xCOztBQUVELDBDQUF5QztBQUN6QztFQUNFO0lBQ0Usa0JBQWlCO0lBQ2xCOztFQUVELHdDQUF1QztFQUN2Qzs7O0lBR0UsaUJBQWdCO0lBQ2hCLGtCQUFpQjtJQUNsQjtFQUNELDZCQUE0QjtFQUM1QjtJQUNFLHFCQUFvQjtJQUNyQjtFQUNELGtFQUFpRTtFQUNqRTtJQUNFLGtCQUFpQjtJQUNsQjtFQUNGIiwiZmlsZSI6Im1haW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmJyb3dzZWhhcHB5IHtcbiAgbWFyZ2luOiAwLjJlbSAwO1xuICBiYWNrZ3JvdW5kOiAjY2NjO1xuICBjb2xvcjogIzAwMDtcbiAgcGFkZGluZzogMC4yZW0gMDtcbn1cblxuYm9keSB7XG4gIHBhZGRpbmc6IDA7XG59XG5cbi8qIEV2ZXJ5dGhpbmcgYnV0IHRoZSBqdW1ib3Ryb24gZ2V0cyBzaWRlIHNwYWNpbmcgZm9yIG1vYmlsZSBmaXJzdCB2aWV3cyAqL1xuLmhlYWRlcixcbi5tYXJrZXRpbmcsXG4uZm9vdGVyIHtcbiAgcGFkZGluZy1sZWZ0OiAxNXB4O1xuICBwYWRkaW5nLXJpZ2h0OiAxNXB4O1xufVxuXG4vKiBDdXN0b20gcGFnZSBoZWFkZXIgKi9cbi5oZWFkZXIge1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2U1ZTVlNTtcbiAgbWFyZ2luLWJvdHRvbTogMTBweDtcbn1cbi8qIE1ha2UgdGhlIG1hc3RoZWFkIGhlYWRpbmcgdGhlIHNhbWUgaGVpZ2h0IGFzIHRoZSBuYXZpZ2F0aW9uICovXG4uaGVhZGVyIGgzIHtcbiAgbWFyZ2luLXRvcDogMDtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbiAgbGluZS1oZWlnaHQ6IDQwcHg7XG4gIHBhZGRpbmctYm90dG9tOiAxOXB4O1xufVxuXG4vKiBDdXN0b20gcGFnZSBmb290ZXIgKi9cbi5mb290ZXIge1xuICBwYWRkaW5nLXRvcDogMTlweDtcbiAgY29sb3I6ICM3Nzc7XG4gIGJvcmRlci10b3A6IDFweCBzb2xpZCAjZTVlNWU1O1xufVxuXG4uY29udGFpbmVyLW5hcnJvdyA+IGhyIHtcbiAgbWFyZ2luOiAzMHB4IDA7XG59XG5cbi8qIE1haW4gbWFya2V0aW5nIG1lc3NhZ2UgYW5kIHNpZ24gdXAgYnV0dG9uICovXG4uanVtYm90cm9uIHtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2U1ZTVlNTtcbn1cbi5qdW1ib3Ryb24gLmJ0biB7XG4gIGZvbnQtc2l6ZTogMjFweDtcbiAgcGFkZGluZzogMTRweCAyNHB4O1xufVxuXG4vKiBTdXBwb3J0aW5nIG1hcmtldGluZyBjb250ZW50ICovXG4ubWFya2V0aW5nIHtcbiAgbWFyZ2luOiA0MHB4IDA7XG59XG4ubWFya2V0aW5nIHAgKyBoNCB7XG4gIG1hcmdpbi10b3A6IDI4cHg7XG59XG5cbi8qIFJlc3BvbnNpdmU6IFBvcnRyYWl0IHRhYmxldHMgYW5kIHVwICovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAuY29udGFpbmVyIHtcbiAgICBtYXgtd2lkdGg6IDczMHB4O1xuICB9XG5cbiAgLyogUmVtb3ZlIHRoZSBwYWRkaW5nIHdlIHNldCBlYXJsaWVyICovXG4gIC5oZWFkZXIsXG4gIC5tYXJrZXRpbmcsXG4gIC5mb290ZXIge1xuICAgIHBhZGRpbmctbGVmdDogMDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAwO1xuICB9XG4gIC8qIFNwYWNlIG91dCB0aGUgbWFzdGhlYWQgKi9cbiAgLmhlYWRlciB7XG4gICAgbWFyZ2luLWJvdHRvbTogMzBweDtcbiAgfVxuICAvKiBSZW1vdmUgdGhlIGJvdHRvbSBib3JkZXIgb24gdGhlIGp1bWJvdHJvbiBmb3IgdmlzdWFsIGVmZmVjdCAqL1xuICAuanVtYm90cm9uIHtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICB9XG59XG4iXX0= */ \ No newline at end of file +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0VBQ0UsaUJBQWdCO0VBQ2hCLGtCQUFpQjtFQUNqQixhQUFZO0VBQ1osa0JBQWlCO0VBQ2xCOztBQUVEO0VBQ0UsWUFBVztFQUNaOztBQUVELDRFQUEyRTtBQUMzRTs7O0VBR0Usb0JBQW1CO0VBQ25CLHFCQUFvQjtFQUNyQjs7QUFFRCx5QkFBd0I7QUFDeEI7RUFDRSxrQ0FBaUM7RUFDakMscUJBQW9CO0VBQ3JCO0FBQ0Qsa0VBQWlFO0FBQ2pFO0VBQ0UsZUFBYztFQUNkLGtCQUFpQjtFQUNqQixtQkFBa0I7RUFDbEIsc0JBQXFCO0VBQ3RCOztBQUVELHlCQUF3QjtBQUN4QjtFQUNFLG1CQUFrQjtFQUNsQixhQUFZO0VBQ1osK0JBQThCO0VBQy9COztBQUVEO0VBQ0UsZ0JBQWU7RUFDaEI7O0FBRUQsZ0RBQStDO0FBQy9DO0VBQ0Usb0JBQW1CO0VBQ25CLGtDQUFpQztFQUNsQztBQUNEO0VBQ0UsaUJBQWdCO0VBQ2hCLG9CQUFtQjtFQUNwQjs7QUFFRCxtQ0FBa0M7QUFDbEM7RUFDRSxnQkFBZTtFQUNoQjtBQUNEO0VBQ0Usa0JBQWlCO0VBQ2xCOztBQUVELDBDQUF5QztBQUN6QztFQUNFO0lBQ0Usa0JBQWlCO0lBQ2xCOztFQUVELHdDQUF1QztFQUN2Qzs7O0lBR0UsaUJBQWdCO0lBQ2hCLGtCQUFpQjtJQUNsQjtFQUNELDZCQUE0QjtFQUM1QjtJQUNFLHFCQUFvQjtJQUNyQjtFQUNELGtFQUFpRTtFQUNqRTtJQUNFLGtCQUFpQjtJQUNsQjs7RUFFRDtNQUNJLHFCQUFvQjtJQUN2Qjs7RUFFRDtNQUNJLDJCQUEwQjtFQUMvQjtFQUNBIiwiZmlsZSI6Im1haW4uY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmJyb3dzZWhhcHB5IHtcbiAgbWFyZ2luOiAwLjJlbSAwO1xuICBiYWNrZ3JvdW5kOiAjY2NjO1xuICBjb2xvcjogIzAwMDtcbiAgcGFkZGluZzogMC4yZW0gMDtcbn1cblxuYm9keSB7XG4gIHBhZGRpbmc6IDA7XG59XG5cbi8qIEV2ZXJ5dGhpbmcgYnV0IHRoZSBqdW1ib3Ryb24gZ2V0cyBzaWRlIHNwYWNpbmcgZm9yIG1vYmlsZSBmaXJzdCB2aWV3cyAqL1xuLmhlYWRlcixcbi5tYXJrZXRpbmcsXG4uZm9vdGVyIHtcbiAgcGFkZGluZy1sZWZ0OiAxNXB4O1xuICBwYWRkaW5nLXJpZ2h0OiAxNXB4O1xufVxuXG4vKiBDdXN0b20gcGFnZSBoZWFkZXIgKi9cbi5oZWFkZXIge1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2U1ZTVlNTtcbiAgbWFyZ2luLWJvdHRvbTogMTBweDtcbn1cbi8qIE1ha2UgdGhlIG1hc3RoZWFkIGhlYWRpbmcgdGhlIHNhbWUgaGVpZ2h0IGFzIHRoZSBuYXZpZ2F0aW9uICovXG4uaGVhZGVyIGgzIHtcbiAgbWFyZ2luLXRvcDogMDtcbiAgbWFyZ2luLWJvdHRvbTogMDtcbiAgbGluZS1oZWlnaHQ6IDQwcHg7XG4gIHBhZGRpbmctYm90dG9tOiAxOXB4O1xufVxuXG4vKiBDdXN0b20gcGFnZSBmb290ZXIgKi9cbi5mb290ZXIge1xuICBwYWRkaW5nLXRvcDogMTlweDtcbiAgY29sb3I6ICM3Nzc7XG4gIGJvcmRlci10b3A6IDFweCBzb2xpZCAjZTVlNWU1O1xufVxuXG4uY29udGFpbmVyLW5hcnJvdyA+IGhyIHtcbiAgbWFyZ2luOiAzMHB4IDA7XG59XG5cbi8qIE1haW4gbWFya2V0aW5nIG1lc3NhZ2UgYW5kIHNpZ24gdXAgYnV0dG9uICovXG4uanVtYm90cm9uIHtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2U1ZTVlNTtcbn1cbi5qdW1ib3Ryb24gLmJ0biB7XG4gIGZvbnQtc2l6ZTogMjFweDtcbiAgcGFkZGluZzogMTRweCAyNHB4O1xufVxuXG4vKiBTdXBwb3J0aW5nIG1hcmtldGluZyBjb250ZW50ICovXG4ubWFya2V0aW5nIHtcbiAgbWFyZ2luOiA0MHB4IDA7XG59XG4ubWFya2V0aW5nIHAgKyBoNCB7XG4gIG1hcmdpbi10b3A6IDI4cHg7XG59XG5cbi8qIFJlc3BvbnNpdmU6IFBvcnRyYWl0IHRhYmxldHMgYW5kIHVwICovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAuY29udGFpbmVyIHtcbiAgICBtYXgtd2lkdGg6IDczMHB4O1xuICB9XG5cbiAgLyogUmVtb3ZlIHRoZSBwYWRkaW5nIHdlIHNldCBlYXJsaWVyICovXG4gIC5oZWFkZXIsXG4gIC5tYXJrZXRpbmcsXG4gIC5mb290ZXIge1xuICAgIHBhZGRpbmctbGVmdDogMDtcbiAgICBwYWRkaW5nLXJpZ2h0OiAwO1xuICB9XG4gIC8qIFNwYWNlIG91dCB0aGUgbWFzdGhlYWQgKi9cbiAgLmhlYWRlciB7XG4gICAgbWFyZ2luLWJvdHRvbTogMzBweDtcbiAgfVxuICAvKiBSZW1vdmUgdGhlIGJvdHRvbSBib3JkZXIgb24gdGhlIGp1bWJvdHJvbiBmb3IgdmlzdWFsIGVmZmVjdCAqL1xuICAuanVtYm90cm9uIHtcbiAgICBib3JkZXItYm90dG9tOiAwO1xuICB9XG5cbiAgLm90aGVyLWNvbG9ye1xuICAgICAgYmFja2dyb3VuZDogI2RiZWFmMDtcbiAgfVxuXG4gIC53ZWxse1xuICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2RiZWFmMDtcbn1cbn1cbiJdfQ== */ \ No newline at end of file diff --git a/static/app/.DS_Store b/static/app/.DS_Store new file mode 100755 index 0000000..b4b06a8 Binary files /dev/null and b/static/app/.DS_Store differ diff --git a/static/app/.buildignore b/static/app/.buildignore old mode 100644 new mode 100755 diff --git a/static/app/.htaccess b/static/app/.htaccess old mode 100644 new mode 100755 diff --git a/static/app/404.html b/static/app/404.html old mode 100644 new mode 100755 diff --git a/static/app/favicon.ico b/static/app/favicon.ico old mode 100644 new mode 100755 diff --git a/static/app/images/noun_28242_cc.png b/static/app/images/noun_28242_cc.png new file mode 100755 index 0000000..396a4eb Binary files /dev/null and b/static/app/images/noun_28242_cc.png differ diff --git a/static/app/images/yeoman.png b/static/app/images/yeoman.png old mode 100644 new mode 100755 diff --git a/static/app/index.html b/static/app/index.html old mode 100644 new mode 100755 index 4f80e08..6fb15e0 --- a/static/app/index.html +++ b/static/app/index.html @@ -8,6 +8,7 @@ + @@ -30,18 +31,27 @@ + declutter + - @@ -53,7 +63,10 @@ @@ -71,7 +84,9 @@ + + @@ -83,8 +98,17 @@ + + + + + + + + + diff --git a/static/app/index.html.orig b/static/app/index.html.orig new file mode 100755 index 0000000..c02e551 --- /dev/null +++ b/static/app/index.html.orig @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +<<<<<<< HEAD + + +======= + + +>>>>>>> b064e6fde9b41fb5cddd1609956abd9b9a50b9c9 + + + diff --git a/static/app/robots.txt b/static/app/robots.txt old mode 100644 new mode 100755 diff --git a/static/app/scripts/app.js b/static/app/scripts/app.js old mode 100644 new mode 100755 index 074c099..bc00d10 --- a/static/app/scripts/app.js +++ b/static/app/scripts/app.js @@ -1,13 +1,5 @@ 'use strict'; -/** - * @ngdoc overview - * @name declutterApp - * @description - * # declutterApp - * - * Main module of the application. - */ angular .module('declutterApp', [ 'ngAnimate', @@ -20,54 +12,94 @@ angular .config(function ($routeProvider) { $routeProvider .when('/', { - templateUrl: 'views/main.html', + templateUrl: '/views/main.html', controller: 'MainCtrl' }) .when('/about', { - templateUrl: 'views/about.html', + templateUrl: '/views/about.html', controller: 'AboutCtrl' }) .when('/login', { - templateURL: 'views/login.html', + templateUrl: 'views/login.html', controller: 'LoginCtrl' - }) + }) .when('/logout', { - templateURL: 'views/logout.html', + templateUrl: 'views/logout.html', controller: 'LogoutCtrl' - }) + }) .when('/register', { - templateURL: 'views/register.html', + templateUrl: 'views/register.html', controller: 'RegisterCtrl' - }) + }) .when('/friendscrap', { - templateURL: 'views/friendscrap.html', - controller: 'FriendscrapCtrl' + templateUrl: 'views/friendscrap.html', + controller: 'FriendscrapCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .when('/mycrap', { - templateURL: 'views/mycrap.html', - controller: 'MycrapCtrl' + templateUrl: 'views/mycrap.html', + controller: 'MycrapCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .when('/mycrap/myclaimed', { - templateURL: 'views/myclaimed.html', - controller: 'MycrapCtrl' + templateUrl: 'views/myclaimed.html', + controller: 'MyclaimedCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .when('/mycrap/postitem', { - templateURL: 'views/postitem.html', - controller: 'MycrapCtrl' + templateUrl: 'views/postitem.html', + controller: 'MycrapCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .when('/mycrap/:item', { - templateURL: 'views/edititem.html', - controller: 'MycrapCtrl' + templateUrl: 'views/edititem.html', + controller: 'MycrapCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .when('/myfriends', { - templateURL: 'views/myfriends.html', - controller: 'MyfriendsCtrl' + templateUrl: 'views/myfriends.html', + controller: 'MyfriendsCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .when('/myfriends/addfriend', { - templateURL: 'views/addfriend.html', - controller: 'MyfriendsCtrl' + templateUrl: 'views/addfriend.html', + controller: 'MyfriendsCtrl', + resolve: { + authenticated: ['djangoAuth', function(djangoAuth){ + return djangoAuth.authenticationStatus(); + }], + } }) .otherwise({ redirectTo: '/' }); - }); + }).config(function($httpProvider){ + $httpProvider.defaults.xsrfCookieName = 'csrftoken'; + $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; + }).config(function($resourceProvider) { + $resourceProvider.defaults.stripTrailingSlashes = false; + }); diff --git a/static/app/scripts/controllers/about.js b/static/app/scripts/controllers/about.js old mode 100644 new mode 100755 index 1159518..2a72572 --- a/static/app/scripts/controllers/about.js +++ b/static/app/scripts/controllers/about.js @@ -14,4 +14,6 @@ angular.module('declutterApp') 'AngularJS', 'Karma' ]; + + console.log('AboutCtrl loaded'); }); diff --git a/static/app/scripts/controllers/config.js b/static/app/scripts/controllers/config.js new file mode 100755 index 0000000..3804d39 --- /dev/null +++ b/static/app/scripts/controllers/config.js @@ -0,0 +1,17 @@ +angular.module('declutterApp') + // Here we see the name of the controller defined. + // Following the name, we see an anonymous function with one argument. + // These arguments are functionality being injected into the controller. + // Think of this as being like including scripts in HTML. + // Unless they are referenced here, you cannot utilize them. + .config( + function($httpProvider){ + $httpProvider.defaults.xsrfCookieName = 'csrftoken'; + $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken'; + // $httpProvider.defaults.headers.common['Access-Control-Allow-Origin'] = "*"; + // $httpProvider.defaults.headers.common['Access-Control-Allow-Methods'] = ['OPTIONS', 'POST']; + // $httpProvider.defaults.headers.common['Access-Control-Allow-Headers'] = 'Content-Type'; + // $httpProvider.defaults.headers.post["Content-Type"] = "multipart/form-data"; + // $httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"; + // delete $httpProvider.defaults.headers.common['X-Requested-With']; + }); diff --git a/static/app/scripts/controllers/friendscrap.js b/static/app/scripts/controllers/friendscrap.js new file mode 100755 index 0000000..3ac3e9f --- /dev/null +++ b/static/app/scripts/controllers/friendscrap.js @@ -0,0 +1,119 @@ +'use strict'; + +angular.module('declutterApp') + + + .controller('FriendscrapCtrl', function ($scope, $q, $http) { + + // Step 1: Get all the data from Item List API + + var poster_items = $http.get('/api/items/'); + + var posterItems = function() { + var deferred = $q.defer(); + poster_items.success(function (data) { + $scope.arrayOfObjects = []; + for (var i = 0; i < data.length; i++) { + var userId = data[i]["poster"]["id"]; + var userName = data[i]["poster"]["username"]; + var item_Name = data[i]["item_name"]; + var availability = data[i]["availability"]; + var desc = data[i]["description"]; + var item_id = data[i]["id"]; + // var item_index = counter +=1; + + // Initialize with empty object and then add user & item details + var obj = {}; + obj["poster_id"] = userId; + obj["username"] = userName; + obj["item"] = item_Name; + obj["desc"] = desc; + obj["item_id"] = item_id; + obj["availability"] = availability; + // obj["item_index"] = item_index; + + $scope.arrayOfObjects.push(obj); + + } + $scope.poster_items = data; + deferred.resolve($scope.arrayOfObjects); + }). + error(function (data) { + console.log("error with poster_items " + data); + }); + return deferred.promise; + }; + posterItems().then(function(data){ + console.log(data); + }); + + + + // Step 2: Get all followers from follower list + var friends = $http.get('/api/appuser/list/follower/'); + var friendsItems = function() { + var task2 = $q.defer(); + $scope.arrayOfFollowers = []; + + friends.success(function (data1) { + for (var i = 0; i < data1.length; i++) { + $scope.arrayOfFollowers.push(data1[i]["follower"]); + } + $scope.friends = data1; + task2.resolve($scope.arrayOfFollowers); + $scope.currentUser = data1[0]["followee"]; + console.log($scope.currentUser); + }). + error(function (data1) { + console.log("error with friends " + data1); + }); + + return task2.promise; + }; + friendsItems().then(function(followers){ + posterItems().then(function(objects){ + friends_crap(followers, objects); + }); + }); + + + + + // Step 3: This function will produce resulting array of objects + + var friends_crap = function(Followers, Objects){ + $scope.resultArray = []; + + for (var i=0; iThis is the about view.

diff --git a/static/app/views/addfriend.html b/static/app/views/addfriend.html new file mode 100755 index 0000000..d609498 --- /dev/null +++ b/static/app/views/addfriend.html @@ -0,0 +1,14 @@ + +
+
+
+

Find and Connect with More Friends +

+

Email:

+ +
+

+
\ No newline at end of file diff --git a/static/app/views/friendscrap.html b/static/app/views/friendscrap.html new file mode 100755 index 0000000..b785330 --- /dev/null +++ b/static/app/views/friendscrap.html @@ -0,0 +1,37 @@ +
+
+
+

My Friends Items +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
IDUser NameItem DescriptionAvailability
{{ friend.poster_id}} {{ friend.username}} {{ friend.item}} {{ friend.desc}} {{ friend.availability}} ClaimHide
+
+
\ No newline at end of file diff --git a/static/app/views/login.html b/static/app/views/login.html new file mode 100755 index 0000000..24def58 --- /dev/null +++ b/static/app/views/login.html @@ -0,0 +1,20 @@ +
+
+
+ + +
+
{{error}}
+
+ + +
+
{{error}}
+
{{error}}
+
{{error}}
+ +
+
+

Not a member yet? Join now!

+ +
diff --git a/static/app/views/logout.html b/static/app/views/logout.html new file mode 100755 index 0000000..d1d8689 --- /dev/null +++ b/static/app/views/logout.html @@ -0,0 +1,3 @@ +
+
You have logged out.
+
\ No newline at end of file diff --git a/static/app/views/main.html b/static/app/views/main.html old mode 100644 new mode 100755 index 787f33e..d6789c9 --- a/static/app/views/main.html +++ b/static/app/views/main.html @@ -1,23 +1,20 @@ -
-

'Allo, 'Allo!

-

- I'm Yeoman
- Always a pleasure scaffolding your apps. -

-

Splendid!

+ +
+

It's time to decrapify your life!

+

+ Get rid of your unused stuff.
And feel good it's not headed to landfill. +

+

Sign Up

-

HTML5 Boilerplate

- HTML5 Boilerplate is a professional front-end template for building fast, robust, and adaptable web apps or sites. + Getting rid of your stuff can be difficult.

-

Angular

- AngularJS is a toolset for building the framework most suited to your application development. + Things have value to you, whether you have formed a personal attachment to your stuff or you believe you have a practical use for it... someday. Most of the time, "personal value" means guilt or "someday" never comes.

-

Karma

-

Spectacular Test Runner for JavaScript.

-
+

How about giving it to your friends and family who need them?

+
\ No newline at end of file diff --git a/static/app/views/myclaimed.html b/static/app/views/myclaimed.html new file mode 100755 index 0000000..634f569 --- /dev/null +++ b/static/app/views/myclaimed.html @@ -0,0 +1,31 @@ + +
+
+
+

Stuff I'm Taking From My Friends

+

{{ getTotalItems() }} items claimed.

+
+ + + + + + + + + + + + + + + + +
ItemDescriptionOwner
{{ item.item_name }}{{ item.description }}{{ item.poster.username }}
+
+
+ diff --git a/static/app/views/mycrap.html b/static/app/views/mycrap.html new file mode 100755 index 0000000..b20f97a --- /dev/null +++ b/static/app/views/mycrap.html @@ -0,0 +1,39 @@ + +
+
+
+

Stuff I'm Currently Trying to Give Away

+

{{ getTotalItems() }} items posted.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
ItemDescriptionCategoryDate PostedStatusClaimed By
{{ item.item_name }}{{ item.description }}{{ item.category }}{{ item.created | date:'shortDate' }}{{ item.availability | availbool }}{{ item.claimer.username }}
+
+
diff --git a/static/app/views/myfriends.html b/static/app/views/myfriends.html new file mode 100755 index 0000000..96c3a7c --- /dev/null +++ b/static/app/views/myfriends.html @@ -0,0 +1,30 @@ + +
+
+
+

My Friends

+
+ + + + + + + + + + + + + + + + +
UsernameEmail
{{ user.user_name }}{{ user.email }}
+
+
diff --git a/static/app/views/postitem.html b/static/app/views/postitem.html new file mode 100755 index 0000000..46adb7f --- /dev/null +++ b/static/app/views/postitem.html @@ -0,0 +1,28 @@ + +
+
+
+

Post a New Item +

+

Item Name:

+

Description:

+

Category:

+ +
+

+
diff --git a/static/app/views/register.html b/static/app/views/register.html new file mode 100755 index 0000000..1ee80d5 --- /dev/null +++ b/static/app/views/register.html @@ -0,0 +1,32 @@ +
+
+
+
+ + +
+
{{error}}
+
+ + +
+
{{error}}
+
+ + +
+
{{error}}
+
+ + +
+
{{error}}
+ +
+
+

You are already logged in! You don't need to register.

+
+
+
Great! You've just registered. You should receive an email shortly with instructions on how to activate your account.
+
+
\ No newline at end of file diff --git a/static/bower_components/bootstrap/dist/css/bootstrap.css b/static/bower_components/bootstrap/dist/css/bootstrap.css index c46af7d..af7e894 100644 --- a/static/bower_components/bootstrap/dist/css/bootstrap.css +++ b/static/bower_components/bootstrap/dist/css/bootstrap.css @@ -1281,7 +1281,7 @@ p { } .lead { margin-bottom: 20px; - font-size: 16px; + font-size: 12px; font-weight: 300; line-height: 1.4; } @@ -3324,7 +3324,7 @@ fieldset[disabled] .btn-link:focus { .btn-lg, .btn-group-lg > .btn { padding: 10px 16px; - font-size: 18px; + font-size: 15px; line-height: 1.3333333; border-radius: 6px; } @@ -4398,14 +4398,15 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { - color: #333; - background-color: transparent; + color: darkred; + background-color: #dbeaf0; } .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { - color: #555; - background-color: #e7e7e7; + color: darkred; + background-color: #dbeaf0; + } .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:hover, diff --git a/static/test/karma.conf.js b/static/test/karma.conf.js index 7483b46..9d99a1d 100644 --- a/static/test/karma.conf.js +++ b/static/test/karma.conf.js @@ -19,7 +19,9 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ // bower:js + 'bower_components/jquery/dist/jquery.js', 'bower_components/angular/angular.js', + 'bower_components/bootstrap/dist/js/bootstrap.js', 'bower_components/angular-animate/angular-animate.js', 'bower_components/angular-cookies/angular-cookies.js', 'bower_components/angular-resource/angular-resource.js',