diff --git a/.gitignore b/.gitignore index a94e49d..c4580cc 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,11 @@ target/ # App Specific cmsAPI/appinfo.py cmsAPI/appinfo.pyc +cmsAPI/settings.py +cmsAPI/settings.pyc +admin/ +rest_framework/ +static/ #IDE .idea/ \ No newline at end of file diff --git a/MontyPyBlog/__init__.pyc b/MontyPyBlog/__init__.pyc deleted file mode 100644 index 58c0755..0000000 Binary files a/MontyPyBlog/__init__.pyc and /dev/null differ diff --git a/MontyPyBlog/admin.py b/MontyPyBlog/admin.py index ac3d8ad..97ac753 100644 --- a/MontyPyBlog/admin.py +++ b/MontyPyBlog/admin.py @@ -1,7 +1,6 @@ from django.contrib import admin from MontyPyBlog.models import Post from MontyPyBlog.models import User -from MontyPyBlog.models import Security class PostAdmin(admin.ModelAdmin): @@ -10,11 +9,14 @@ class PostAdmin(admin.ModelAdmin): 'author', 'featured_image', 'gallery_images',] # Can include methods here too (recently published, etc) list_display = ( - 'title', 'post_type', 'author') - list_filter = ( - 'author', 'post_type') + 'title', 'post_type', 'author' + ) + list_filter = [ + 'post_type', + ] search_fields = ( - 'author', 'title') + 'author', 'title' + ) # date_hierarchy = ('created_on') @@ -27,5 +29,4 @@ class PostAdmin(admin.ModelAdmin): # inlines = [PostInline] admin.site.register(Post, PostAdmin) -admin.site.register(User) -admin.site.register(Security) +admin.site.register(User) \ No newline at end of file diff --git a/MontyPyBlog/admin.pyc b/MontyPyBlog/admin.pyc deleted file mode 100644 index 3e88816..0000000 Binary files a/MontyPyBlog/admin.pyc and /dev/null differ diff --git a/MontyPyBlog/models.py b/MontyPyBlog/models.py index d38e2ea..4283449 100644 --- a/MontyPyBlog/models.py +++ b/MontyPyBlog/models.py @@ -7,11 +7,6 @@ import bson -""" -@TODO Create/research a proper json serializer for the api -""" - - # Create your models here. class Post(models.Model): title = models.CharField(max_length=255) @@ -68,9 +63,4 @@ def __unicode__(self): 'Last Login' : str(self.last_login), 'Admin' : self.is_staff, } - return self.pk - - -class Security(models.Model): - # Oauth implementation - pass + return self.pk \ No newline at end of file diff --git a/MontyPyBlog/models.pyc b/MontyPyBlog/models.pyc deleted file mode 100644 index 5d9e613..0000000 Binary files a/MontyPyBlog/models.pyc and /dev/null differ diff --git a/MontyPyBlog/serializers.pyc b/MontyPyBlog/serializers.pyc deleted file mode 100644 index 60e940e..0000000 Binary files a/MontyPyBlog/serializers.pyc and /dev/null differ diff --git a/MontyPyBlog/urls.py b/MontyPyBlog/urls.py index 9d23a56..80ca0e1 100644 --- a/MontyPyBlog/urls.py +++ b/MontyPyBlog/urls.py @@ -24,4 +24,6 @@ url(r'^user/login/(?P\w+)/$', views.post_login, name='postLogin'), # Ex: /cms/user/logout/7/ url(r'^user/logout/(?P\w+)/$', views.post_logout, name='postLogout'), + # Ex: /cms/csrf/token/ + url(r'^csrf/token/$', views.get_csrf, name='getToken'), ) diff --git a/MontyPyBlog/urls.pyc b/MontyPyBlog/urls.pyc deleted file mode 100644 index cfbeccd..0000000 Binary files a/MontyPyBlog/urls.pyc and /dev/null differ diff --git a/MontyPyBlog/views.pyc b/MontyPyBlog/views.pyc deleted file mode 100644 index 29fd54f..0000000 Binary files a/MontyPyBlog/views.pyc and /dev/null differ diff --git a/MontyPyBlog/views/post.py b/MontyPyBlog/views/post.py index 3aa6a1d..2d21058 100644 --- a/MontyPyBlog/views/post.py +++ b/MontyPyBlog/views/post.py @@ -1,8 +1,9 @@ from django.http import HttpResponse from MontyPyBlog.models import Post, User -from django.http import Http404 +import time +import django.middleware.csrf -from rest_framework.decorators import api_view, parser_classes +from rest_framework.decorators import api_view, parser_classes, authentication_classes, permission_classes from rest_framework.response import Response from MontyPyBlog.serializers import PostSerializer, UserSerializer from rest_framework import status @@ -13,13 +14,9 @@ import threading import imghdr -from django.views.decorators.csrf import csrf_exempt """ @TODO: -- Uploading files -- Sending uploaded files to an S3 bucket -- Getting the uploaded file slug(s) - Site info in db (base url, base media url, etc) """ @@ -37,6 +34,9 @@ def index(request): @api_view(['GET']) def get_post(request): if request.method == 'GET': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) + try: post_id = request.DATA.get('post_id') post = Post.objects.get(pk=post_id) @@ -48,9 +48,21 @@ def get_post(request): return Response(serializer.data) +# For dev +@api_view(['GET']) +def get_csrf(request): + if request.method == 'GET': + return Response(django.middleware.csrf.get_token(request)) + else: + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + + @api_view(['POST']) def post_post(request): if request.method == 'POST': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) + user = User.objects.get(pk=request.DATA.get('author')) data = { 'title': request.DATA.get('title'), @@ -77,12 +89,15 @@ def post_post(request): @api_view(['PATCH']) def patch_post(request): if request.method == 'PATCH': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) + try: post_id = request.DATA.get('post_id') post = Post.objects.get(pk=post_id) except Post.DoesNotExist: - return Http404 + return Response(status=status.HTTP_404_NOT_FOUND) user_id = request.DATA.get('author') user = User.objects.get(pk=user_id) @@ -115,6 +130,8 @@ def patch_post(request): @parser_classes((MultiPartParser, FormParser)) def post_files(request): if request.method == 'POST': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) post = Post.objects.get(pk=request.DATA.get('post_id')) @@ -128,7 +145,7 @@ def post_files(request): accepted_file_types = ['jpeg', 'gif', 'png'] - def upload(file): + def upload(file, key_name): if imghdr.what(file) not in accepted_file_types: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -139,7 +156,7 @@ def upload(file): bucket = s3.get_bucket(bucket_name) key_object = Key(bucket) - key_object.key = s3_folder_name + file.name + key_object.key = s3_folder_name + key_name key_object.set_contents_from_file(file) key_object.make_public() @@ -147,11 +164,13 @@ def upload(file): return image_url + # Bottleneck will be connecting to S3, so we thread # Research how to get return values for threads file_urls = [] for filename, file in request.FILES.iteritems(): - t = threading.Thread(target=upload, args=(file,)).start() - file_urls.append(os.environ['S3_BUCKET_LOCATION'] + file.name) + key_name = str(time.time()) + '-' + file.name + t = threading.Thread(target=upload, args=(file, key_name)).start() + file_urls.append(os.environ['S3_BUCKET_LOCATION'] + key_name) data = { file_upload_type: ','.join(file_urls), @@ -168,4 +187,4 @@ def upload(file): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) else: - return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) \ No newline at end of file diff --git a/MontyPyBlog/views/user.py b/MontyPyBlog/views/user.py index 4ccdfd9..bfd16c3 100644 --- a/MontyPyBlog/views/user.py +++ b/MontyPyBlog/views/user.py @@ -15,6 +15,9 @@ @api_view(['POST']) def post_user(request): if request.method == 'POST': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) + data = { 'username': request.DATA.get('username'), 'email': request.DATA.get('email'), @@ -36,6 +39,9 @@ def post_user(request): @api_view(['GET']) def get_user(request): if request.method == 'GET': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) + try: user_id = request.DATA.get('user_id') user = User.objects.get(pk=user_id) @@ -59,6 +65,9 @@ def get_user(request): @api_view(['PATCH']) def patch_user(request): if request.method == 'PATCH': + if request.auth is None: + return Response('Not Authenticated', status=status.HTTP_403_FORBIDDEN) + try: user_id = request.DATA.get('user_id') user = User.objects.get(pk=user_id) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a33921 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +MontyPyBlog +=========== + +A CMS API written in Python with Django + +Sample Post data response: +{ + "AuthorId": "544bf200438b4c5ac432fdf1", + "Content": "Post content", + "Created On": "2014-10-25 19:01:41.792000", + "Featured Image": "slog-one", + "Gallery Images": "slugs-one", + "Id": "544bf395438b4c5c3943c907", + "Post Type": "post", + "Title": "Post Title #1" +} + +Sample user data response: +{ + "Admin": true, + "Created On": "2014-10-25 19:05:05.511000", + "Email": "user1@user.com", + "Id": "544bf461438b4c5c3943c909", + "Last Login": "2014-10-25 20:05:03", + "Username": "User1" +} diff --git a/cmsAPI/__init__.pyc b/cmsAPI/__init__.pyc deleted file mode 100644 index 1cc2faf..0000000 Binary files a/cmsAPI/__init__.pyc and /dev/null differ diff --git a/cmsAPI/appinfo_sample.py b/cmsAPI/appinfo_sample.py new file mode 100644 index 0000000..52b42c2 --- /dev/null +++ b/cmsAPI/appinfo_sample.py @@ -0,0 +1,15 @@ +import os, sys + +os.environ['APP_ENVIRONMENT'] = 'local' + +os.environ['AWS_ACCESS_KEY_ID'] = '' +os.environ['AWS_SECRET_ACCESS_KEY'] = '' +os.environ['S3_BUCKET_NAME'] = '' +os.environ['S3_BUCKET_LOCATION'] = '' + +# Unused for now +os.environ['DATABASE_HOST'] = '' +os.environ['DATABASE_USER'] = '' +os.environ['DATABASE_PASS'] = '' + +#os.environ[''] = '' \ No newline at end of file diff --git a/cmsAPI/settings.py b/cmsAPI/settings.py index 76bcd70..64aab6b 100644 --- a/cmsAPI/settings.py +++ b/cmsAPI/settings.py @@ -98,6 +98,7 @@ ) MIDDLEWARE_CLASSES = ( + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -118,6 +119,16 @@ # Don't forget to use absolute paths, not relative paths. ) +# +# CORS +# +CORS_ORIGIN_ALLOW_ALL = False +CORS_ORIGIN_WHITELIST = ( + 'google.com', + 'hostname.example.com', +) + + INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', @@ -129,12 +140,23 @@ 'djangotoolbox', 'django_mongodb_engine', 'rest_framework', + 'corsheaders', + 'provider', + 'provider.oauth2', # Uncomment the next line to enable the admin: 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', ) +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + # 'rest_framework.authentication.BasicAuthentication', + # 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.OAuth2Authentication', + ) +} + SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer' # A sample logging configuration. The only tangible logging diff --git a/cmsAPI/settings.pyc b/cmsAPI/settings.pyc deleted file mode 100644 index 444da61..0000000 Binary files a/cmsAPI/settings.pyc and /dev/null differ diff --git a/cmsAPI/settings_sample.py b/cmsAPI/settings_sample.py new file mode 100644 index 0000000..76fc129 --- /dev/null +++ b/cmsAPI/settings_sample.py @@ -0,0 +1,187 @@ +import appinfo +# Django settings for cmsAPI project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@example.com'), +) + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django_mongodb_engine', + 'NAME': 'cms_api', + 'HOST' : '127.0.0.1', + 'PORT' : '27017', + 'USER' : 'simon', + 'PASSWORD' : 'simon', + }, +} + +# Hosts/domain names that are valid for this site; required if DEBUG is False +# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts +ALLOWED_HOSTS = [] + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# In a Windows environment this must be set to your system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = '544bf2f9438b4c5c2919c96e' + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale. +USE_L10N = True + +# If you set this to False, Django will not use timezone-aware datetimes. +USE_TZ = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/var/www/example.com/media/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://example.com/media/", "http://media.example.com/" +MEDIA_URL = '' + +FILE_UPLOAD_HANDLERS = ( + "django.core.files.uploadhandler.MemoryFileUploadHandler", + "django.core.files.uploadhandler.TemporaryFileUploadHandler" +) + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/var/www/example.com/static/" +STATIC_ROOT = '' + +# URL prefix for static files. +# Example: "http://example.com/static/", "http://static.example.com/" +STATIC_URL = '/static/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# List of finder classes that know how to find static files in +# various locations. +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +# 'django.contrib.staticfiles.finders.DefaultStorageFinder', +) + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'rrq@(@vs34sl0h3o0yw%z#0taznk(kl&wyl-(77u$_&3&2-(&9' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +# 'django.template.loaders.eggs.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + # Uncomment the next line for simple clickjacking protection: + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'cmsAPI.urls' + +# Python dotted path to the WSGI application used by Django's runserver. +WSGI_APPLICATION = 'cmsAPI.wsgi.application' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +# CORS +CORS_ORIGIN_ALLOW_ALL = False +CORS_ORIGIN_WHITELIST = ( + 'google.com', + 'hostname.example.com', +) + + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'MontyPyBlog', + 'djangotoolbox', + 'django_mongodb_engine', + 'rest_framework', + 'corsheaders', + 'provider', + 'provider.oauth2', + # Uncomment the next line to enable the admin: + 'django.contrib.admin', + # Uncomment the next line to enable admin documentation: + # 'django.contrib.admindocs', +) + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + # 'rest_framework.authentication.BasicAuthentication', + # 'rest_framework.authentication.SessionAuthentication', + 'rest_framework.authentication.OAuth2Authentication', + ) +} + +SESSION_SERIALIZER = 'django.contrib.sessions.serializers.JSONSerializer' + +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} \ No newline at end of file diff --git a/cmsAPI/urls.py b/cmsAPI/urls.py index dfeba4e..c94e616 100644 --- a/cmsAPI/urls.py +++ b/cmsAPI/urls.py @@ -17,4 +17,6 @@ url(r'^admin/', include(admin.site.urls)), # Points urlconf to the api.urls, which will then handle our urls url(r'^cms/', include('MontyPyBlog.urls')), + # OAuth2 + url(r'^oauth2/', include('provider.oauth2.urls', namespace='oauth2')), ) diff --git a/cmsAPI/urls.pyc b/cmsAPI/urls.pyc deleted file mode 100644 index 3430228..0000000 Binary files a/cmsAPI/urls.pyc and /dev/null differ diff --git a/cmsAPI/wsgi.pyc b/cmsAPI/wsgi.pyc deleted file mode 100644 index bedc114..0000000 Binary files a/cmsAPI/wsgi.pyc and /dev/null differ