diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..df1b201
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,24 @@
+#################################
+# GitHub Dependabot Config info #
+#################################
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
+
+ # Maintain dependencies for docker
+ - package-ecosystem: "docker"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ open-pull-requests-limit: 10
+
+ # Maintain dependencies for python with pip
+ - package-ecosystem: "pip"
+ directory: "/dependencies"
+ schedule:
+ interval: "daily"
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/code-ql.yml b/.github/workflows/code-ql.yml
new file mode 100644
index 0000000..882cfea
--- /dev/null
+++ b/.github/workflows/code-ql.yml
@@ -0,0 +1,51 @@
+name: "Code Scanning - Action"
+
+on:
+ push:
+ pull_request:
+ schedule:
+ # ┌───────────── minute (0 - 59)
+ # │ ┌───────────── hour (0 - 23)
+ # │ │ ┌───────────── day of the month (1 - 31)
+ # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
+ # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
+ # │ │ │ │ │
+ # │ │ │ │ │
+ # │ │ │ │ │
+ # * * * * *
+ - cron: '30 1 * * 0'
+
+jobs:
+ CodeQL-Build:
+ # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ # Override language selection by uncommenting this and choosing your languages
+ with:
+ languages: python
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below).
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following
+ # three lines and modify them (or add more) to build your code if your
+ # project uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
new file mode 100644
index 0000000..0292cea
--- /dev/null
+++ b/.github/workflows/python-app.yml
@@ -0,0 +1,54 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Python application
+
+on:
+ push:
+ branches-ignore: master
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v2
+
+ - name: Set up Python 3.5
+ uses: actions/setup-python@v2
+ with:
+ python-version: 3.5.4
+ # 3.5.2 was not found
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install pytest pipenv
+ # if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ pipenv install --clear --system
+
+ - name: Install pyenv and pyenv-virtualenv
+ run: |
+ brew update
+ brew install pyenv pyenv-virtualenv
+
+ - name: Pyenv install
+ run: |
+ pyenv install 3.5.4
+ pyenv virtualenv 3.5.4 productionready
+ pyenv local productionready
+ pyenv rehash
+
+ - name: Lint with Super-Linter
+ # uses: github/super-linter@v3
+ # env:
+ # VALIDATE_ALL_CODEBASE: false
+ # DEFAULT_BRANCH: master
+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "Would have run linting across code base and failed on errors"
+
+ - name: Test with pytest
+ run: |
+ pytest .
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..f114d52
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,21 @@
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ stale:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/stale@v3
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ stale-issue-message: 'Stale issue message'
+ stale-pr-message: 'Stale pull request message'
+ stale-issue-label: 'no-issue-activity'
+ stale-pr-label: 'no-pr-activity'
+ days-before-stale: 5
+ days-before-close: 6
diff --git a/README.md b/README.md
index a90f2e3..08dfc78 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,21 @@
# 
-> ### Example Django DRF codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld-example-apps) API spec.
+# Example Django DRF codebase containing real world examples (CRUD, auth,
+# advanced patterns, etc) that adheres to the
+# [RealWorld](https://github.com/gothinkster/realworld-example-apps) API
+# spec.
+>
-
+
< /a >
This repo is functionality complete — PR's and issues welcome!
-## Installation
+# Installation
-1. Clone this repository: `git clone git@github.com:gothinkster/productionready-django-api.git`.
-2. `cd` into `conduit-django`: `cd productionready-django-api`.
-3. Install [pyenv](https://github.com/yyuu/pyenv#installation).
-4. Install [pyenv-virtualenv](https://github.com/yyuu/pyenv-virtualenv#installation).
+1. Clone this repository: `git clone git @ github.com: gothinkster / productionready - django - api.git`.
+2. `cd` into `conduit - django`: `cd productionready - django - api`.
+3. Install[pyenv](https: // github.com / yyuu / pyenv # installation).
+4. Install[pyenv - virtualenv](https: // github.com / yyuu / pyenv - virtualenv # installation).
5. Install Python 3.5.2: `pyenv install 3.5.2`.
6. Create a new virtualenv called `productionready`: `pyenv virtualenv 3.5.2 productionready`.
7. Set the local virtualenv to `productionready`: `pyenv local productionready`.
@@ -19,6 +23,6 @@ This repo is functionality complete — PR's and issues welcome!
If all went well then your command line prompt should now start with `(productionready)`.
-If your command line prompt does not start with `(productionready)` at this point, try running `pyenv activate productionready` or `cd ../productionready-django-api`.
+If your command line prompt does not start with `(productionready)` at this point, try running `pyenv activate productionready` or `cd .. / productionready - django - api`.
If pyenv is still not working, visit us in the Thinkster Slack channel so we can help you out.
diff --git a/conduit/apps/articles/__init__.py b/conduit/apps/articles/__init__.py
index c6be3f5..1e64079 100644
--- a/conduit/apps/articles/__init__.py
+++ b/conduit/apps/articles/__init__.py
@@ -9,4 +9,5 @@ class ArticlesAppConfig(AppConfig):
def ready(self):
import conduit.apps.articles.signals
+
default_app_config = 'conduit.apps.articles.ArticlesAppConfig'
diff --git a/conduit/apps/articles/migrations/0001_initial.py b/conduit/apps/articles/migrations/0001_initial.py
index 2898383..a655c25 100644
--- a/conduit/apps/articles/migrations/0001_initial.py
+++ b/conduit/apps/articles/migrations/0001_initial.py
@@ -2,8 +2,8 @@
# Generated by Django 1.10 on 2016-08-28 15:43
from __future__ import unicode_literals
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
@@ -18,14 +18,16 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Article',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('id', models.AutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('slug', models.SlugField(max_length=255, unique=True)),
('title', models.CharField(db_index=True, max_length=255)),
('description', models.TextField()),
('body', models.TextField()),
- ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='articles', to='profiles.Profile')),
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ related_name='articles', to='profiles.Profile')),
],
options={
'abstract': False,
diff --git a/conduit/apps/articles/migrations/0002_comment.py b/conduit/apps/articles/migrations/0002_comment.py
index f66f188..8c21511 100644
--- a/conduit/apps/articles/migrations/0002_comment.py
+++ b/conduit/apps/articles/migrations/0002_comment.py
@@ -2,8 +2,8 @@
# Generated by Django 1.10 on 2016-08-28 16:00
from __future__ import unicode_literals
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
@@ -17,12 +17,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Comment',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('id', models.AutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('body', models.TextField()),
- ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='articles.Article')),
- ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='profiles.Profile')),
+ ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ related_name='comments', to='articles.Article')),
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+ related_name='comments', to='profiles.Profile')),
],
options={
'ordering': ['-created_at', '-updated_at'],
diff --git a/conduit/apps/articles/migrations/0003_auto_20160828_1656.py b/conduit/apps/articles/migrations/0003_auto_20160828_1656.py
index bacc6dc..9fd5749 100644
--- a/conduit/apps/articles/migrations/0003_auto_20160828_1656.py
+++ b/conduit/apps/articles/migrations/0003_auto_20160828_1656.py
@@ -15,7 +15,8 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Tag',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('id', models.AutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('tag', models.CharField(max_length=255)),
@@ -29,6 +30,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='article',
name='tags',
- field=models.ManyToManyField(related_name='articles', to='articles.Tag'),
+ field=models.ManyToManyField(
+ related_name='articles', to='articles.Tag'),
),
]
diff --git a/conduit/apps/articles/signals.py b/conduit/apps/articles/signals.py
index 5cb9a0a..a5b8b6d 100644
--- a/conduit/apps/articles/signals.py
+++ b/conduit/apps/articles/signals.py
@@ -6,6 +6,7 @@
from .models import Article
+
@receiver(pre_save, sender=Article)
def add_slug_to_article_if_not_exists(sender, instance, *args, **kwargs):
MAXIMUM_SLUG_LENGTH = 255
diff --git a/conduit/apps/articles/urls.py b/conduit/apps/articles/urls.py
index ed3e46e..5ceafc4 100644
--- a/conduit/apps/articles/urls.py
+++ b/conduit/apps/articles/urls.py
@@ -1,11 +1,9 @@
from django.conf.urls import include, url
-
from rest_framework.routers import DefaultRouter
-from .views import (
- ArticleViewSet, ArticlesFavoriteAPIView, ArticlesFeedAPIView,
- CommentsListCreateAPIView, CommentsDestroyAPIView, TagListAPIView
-)
+from .views import (ArticlesFavoriteAPIView, ArticlesFeedAPIView,
+ ArticleViewSet, CommentsDestroyAPIView,
+ CommentsListCreateAPIView, TagListAPIView)
router = DefaultRouter(trailing_slash=False)
router.register(r'articles', ArticleViewSet)
@@ -18,7 +16,7 @@
url(r'^articles/(?P[-\w]+)/favorite/?$',
ArticlesFavoriteAPIView.as_view()),
- url(r'^articles/(?P[-\w]+)/comments/?$',
+ url(r'^articles/(?P[-\w]+)/comments/?$',
CommentsListCreateAPIView.as_view()),
url(r'^articles/(?P[-\w]+)/comments/(?P[\d]+)/?$',
diff --git a/conduit/apps/articles/views.py b/conduit/apps/articles/views.py
index 5da36d6..3804b5d 100644
--- a/conduit/apps/articles/views.py
+++ b/conduit/apps/articles/views.py
@@ -1,8 +1,7 @@
from rest_framework import generics, mixins, status, viewsets
from rest_framework.exceptions import NotFound
-from rest_framework.permissions import (
- AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly
-)
+from rest_framework.permissions import (AllowAny, IsAuthenticated,
+ IsAuthenticatedOrReadOnly)
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -11,7 +10,7 @@
from .serializers import ArticleSerializer, CommentSerializer, TagSerializer
-class ArticleViewSet(mixins.CreateModelMixin,
+class ArticleViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet):
@@ -49,7 +48,7 @@ def create(self, request):
serializer_data = request.data.get('article', {})
serializer = self.serializer_class(
- data=serializer_data, context=serializer_context
+ data=serializer_data, context=serializer_context
)
serializer.is_valid(raise_exception=True)
serializer.save()
@@ -83,7 +82,6 @@ def retrieve(self, request, slug):
return Response(serializer.data, status=status.HTTP_200_OK)
-
def update(self, request, slug):
serializer_context = {'request': request}
@@ -91,13 +89,13 @@ def update(self, request, slug):
serializer_instance = self.queryset.get(slug=slug)
except Article.DoesNotExist:
raise NotFound('An article with this slug does not exist.')
-
+
serializer_data = request.data.get('article', {})
serializer = self.serializer_class(
- serializer_instance,
+ serializer_instance,
context=serializer_context,
- data=serializer_data,
+ data=serializer_data,
partial=True
)
serializer.is_valid(raise_exception=True)
diff --git a/conduit/apps/authentication/__init__.py b/conduit/apps/authentication/__init__.py
index 3df2e3c..e02c569 100644
--- a/conduit/apps/authentication/__init__.py
+++ b/conduit/apps/authentication/__init__.py
@@ -9,6 +9,7 @@ class AuthenticationAppConfig(AppConfig):
def ready(self):
import conduit.apps.authentication.signals
+
# This is how we register our custom app config with Django. Django is smart
# enough to look for the `default_app_config` property of each registered app
# and use the correct app config based on that value.
diff --git a/conduit/apps/authentication/backends.py b/conduit/apps/authentication/backends.py
index 3d9d7fc..31addab 100644
--- a/conduit/apps/authentication/backends.py
+++ b/conduit/apps/authentication/backends.py
@@ -1,7 +1,5 @@
import jwt
-
from django.conf import settings
-
from rest_framework import authentication, exceptions
from .models import User
@@ -33,7 +31,7 @@ def authenticate(self, request):
request.user = None
# `auth_header` should be an array with two elements: 1) the name of
- # the authentication header (in this case, "Token") and 2) the JWT
+ # the authentication header (in this case, "Token") and 2) the JWT
# that we should authenticate against.
auth_header = authentication.get_authorization_header(request).split()
auth_header_prefix = self.authentication_header_prefix.lower()
diff --git a/conduit/apps/authentication/migrations/0001_initial.py b/conduit/apps/authentication/migrations/0001_initial.py
index bbd2d9c..beda6d3 100644
--- a/conduit/apps/authentication/migrations/0001_initial.py
+++ b/conduit/apps/authentication/migrations/0001_initial.py
@@ -17,18 +17,26 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='User',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('password', models.CharField(max_length=128, verbose_name='password')),
- ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
- ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
- ('username', models.CharField(db_index=True, max_length=255, unique=True)),
- ('email', models.EmailField(db_index=True, max_length=254, unique=True)),
+ ('id', models.AutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
+ ('password', models.CharField(
+ max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(
+ blank=True, null=True, verbose_name='last login')),
+ ('is_superuser', models.BooleanField(default=False,
+ help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+ ('username', models.CharField(
+ db_index=True, max_length=255, unique=True)),
+ ('email', models.EmailField(
+ db_index=True, max_length=254, unique=True)),
('is_active', models.BooleanField(default=True)),
('is_staff', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
- ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
- ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
+ related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.',
+ related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'abstract': False,
diff --git a/conduit/apps/authentication/models.py b/conduit/apps/authentication/models.py
index 2129957..a6a6686 100644
--- a/conduit/apps/authentication/models.py
+++ b/conduit/apps/authentication/models.py
@@ -1,11 +1,9 @@
-import jwt
-
from datetime import datetime, timedelta
+import jwt
from django.conf import settings
-from django.contrib.auth.models import (
- AbstractBaseUser, BaseUserManager, PermissionsMixin
-)
+from django.contrib.auth.models import (AbstractBaseUser, BaseUserManager,
+ PermissionsMixin)
from django.db import models
from conduit.apps.core.models import TimestampedModel
@@ -36,21 +34,21 @@ def create_user(self, username, email, password=None):
return user
def create_superuser(self, username, email, password):
- """
- Create and return a `User` with superuser powers.
+ """
+ Create and return a `User` with superuser powers.
- Superuser powers means that this use is an admin that can do anything
- they want.
- """
- if password is None:
- raise TypeError('Superusers must have a password.')
+ Superuser powers means that this use is an admin that can do anything
+ they want.
+ """
+ if password is None:
+ raise TypeError('Superusers must have a password.')
- user = self.create_user(username, email, password)
- user.is_superuser = True
- user.is_staff = True
- user.save()
+ user = self.create_user(username, email, password)
+ user.is_superuser = True
+ user.is_staff = True
+ user.save()
- return user
+ return user
class User(AbstractBaseUser, PermissionsMixin, TimestampedModel):
@@ -109,12 +107,12 @@ def token(self):
return self._generate_jwt_token()
def get_full_name(self):
- """
- This method is required by Django for things like handling emails.
- Typically, this would be the user's first and last name. Since we do
- not store the user's real name, we return their username instead.
- """
- return self.username
+ """
+ This method is required by Django for things like handling emails.
+ Typically, this would be the user's first and last name. Since we do
+ not store the user's real name, we return their username instead.
+ """
+ return self.username
def get_short_name(self):
"""
diff --git a/conduit/apps/authentication/serializers.py b/conduit/apps/authentication/serializers.py
index d9894ce..e441eb9 100644
--- a/conduit/apps/authentication/serializers.py
+++ b/conduit/apps/authentication/serializers.py
@@ -1,5 +1,4 @@
from django.contrib.auth import authenticate
-
from rest_framework import serializers
from conduit.apps.profiles.serializers import ProfileSerializer
@@ -84,8 +83,6 @@ def validate(self, data):
'This user has been deactivated.'
)
-
-
# The `validate` method should return a dictionary of validated data.
# This is the data that is passed to the `create` and `update` methods
# that we will see later on.
@@ -99,7 +96,7 @@ def validate(self, data):
class UserSerializer(serializers.ModelSerializer):
"""Handles serialization and deserialization of User objects."""
- # Passwords must be at least 8 characters, but no more than 128
+ # Passwords must be at least 8 characters, but no more than 128
# characters. These values are the default provided by Django. We could
# change them, but that would create extra work while introducing no real
# benefit, so let's just stick with the defaults.
@@ -113,7 +110,7 @@ class UserSerializer(serializers.ModelSerializer):
# so. Moreover, `UserSerializer` should never expose profile information,
# so we set `write_only=True`.
profile = ProfileSerializer(write_only=True)
-
+
# We want to get the `bio` and `image` fields from the related Profile
# model.
bio = serializers.CharField(source='profile.bio', read_only=True)
@@ -122,7 +119,7 @@ class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
- 'email', 'username', 'password', 'token', 'profile', 'bio',
+ 'email', 'username', 'password', 'token', 'profile', 'bio',
'image',
)
@@ -130,12 +127,11 @@ class Meta:
# specifying the field with `read_only=True` like we did for password
# above. The reason we want to use `read_only_fields` here is because
# we don't need to specify anything else about the field. For the
- # password field, we needed to specify the `min_length` and
+ # password field, we needed to specify the `min_length` and
# `max_length` properties too, but that isn't the case for the token
# field.
read_only_fields = ('token',)
-
def update(self, instance, validated_data):
"""Performs an update on a User."""
@@ -169,7 +165,7 @@ def update(self, instance, validated_data):
# We're doing the same thing as above, but this time we're making
# changes to the Profile model.
setattr(instance.profile, key, value)
-
+
# Save the profile just like we saved the user.
instance.profile.save()
diff --git a/conduit/apps/authentication/signals.py b/conduit/apps/authentication/signals.py
index 2e5168f..4df1bbd 100644
--- a/conduit/apps/authentication/signals.py
+++ b/conduit/apps/authentication/signals.py
@@ -5,6 +5,7 @@
from .models import User
+
@receiver(post_save, sender=User)
def create_related_profile(sender, instance, created, *args, **kwargs):
# Notice that we're checking for `created` here. We only want to do this
diff --git a/conduit/apps/authentication/urls.py b/conduit/apps/authentication/urls.py
index 2a594ca..3f3e3b1 100644
--- a/conduit/apps/authentication/urls.py
+++ b/conduit/apps/authentication/urls.py
@@ -1,8 +1,6 @@
from django.conf.urls import url
-from .views import (
- LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView
-)
+from .views import LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView
urlpatterns = [
url(r'^user/?$', UserRetrieveUpdateAPIView.as_view()),
diff --git a/conduit/apps/authentication/views.py b/conduit/apps/authentication/views.py
index b12d867..880ccd4 100644
--- a/conduit/apps/authentication/views.py
+++ b/conduit/apps/authentication/views.py
@@ -5,9 +5,8 @@
from rest_framework.views import APIView
from .renderers import UserJSONRenderer
-from .serializers import (
- LoginSerializer, RegistrationSerializer, UserSerializer
-)
+from .serializers import (LoginSerializer, RegistrationSerializer,
+ UserSerializer)
class RegistrationAPIView(APIView):
@@ -82,4 +81,3 @@ def update(self, request, *args, **kwargs):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
-
diff --git a/conduit/apps/core/exceptions.py b/conduit/apps/core/exceptions.py
index 44baf3c..fdd43f7 100644
--- a/conduit/apps/core/exceptions.py
+++ b/conduit/apps/core/exceptions.py
@@ -1,5 +1,6 @@
from rest_framework.views import exception_handler
+
def core_exception_handler(exc, context):
# If an exception is thrown that we don't explicitly handle here, we want
# to delegate to the default exception handler offered by DRF. If we do
@@ -17,12 +18,13 @@ def core_exception_handler(exc, context):
if exception_class in handlers:
# If this exception is one that we can handle, handle it. Otherwise,
- # return the response generated earlier by the default exception
+ # return the response generated earlier by the default exception
# handler.
return handlers[exception_class](exc, context, response)
return response
+
def _handle_generic_error(exc, context, response):
# This is about the most straightforward exception handler we can create.
# We take the response generated by DRF and wrap it in the `errors` key.
@@ -32,6 +34,7 @@ def _handle_generic_error(exc, context, response):
return response
+
def _handle_not_found_error(exc, context, response):
view = context.get('view', None)
diff --git a/conduit/apps/core/utils.py b/conduit/apps/core/utils.py
index 48a09ac..20e12e1 100644
--- a/conduit/apps/core/utils.py
+++ b/conduit/apps/core/utils.py
@@ -3,5 +3,6 @@
DEFAULT_CHAR_STRING = string.ascii_lowercase + string.digits
+
def generate_random_string(chars=DEFAULT_CHAR_STRING, size=6):
return ''.join(random.choice(chars) for _ in range(size))
diff --git a/conduit/apps/profiles/migrations/0001_initial.py b/conduit/apps/profiles/migrations/0001_initial.py
index 85825ec..12bd12b 100644
--- a/conduit/apps/profiles/migrations/0001_initial.py
+++ b/conduit/apps/profiles/migrations/0001_initial.py
@@ -2,9 +2,9 @@
# Generated by Django 1.10 on 2016-08-28 15:06
from __future__ import unicode_literals
+import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
-import django.db.models.deletion
class Migration(migrations.Migration):
@@ -19,12 +19,14 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Profile',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('id', models.AutoField(auto_created=True,
+ primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('bio', models.TextField(blank=True)),
('image', models.URLField(blank=True)),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('user', models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created_at', '-updated_at'],
diff --git a/conduit/apps/profiles/migrations/0002_profile_follows.py b/conduit/apps/profiles/migrations/0002_profile_follows.py
index 45dfbdc..9685e9e 100644
--- a/conduit/apps/profiles/migrations/0002_profile_follows.py
+++ b/conduit/apps/profiles/migrations/0002_profile_follows.py
@@ -15,6 +15,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='follows',
- field=models.ManyToManyField(related_name='followed_by', to='profiles.Profile'),
+ field=models.ManyToManyField(
+ related_name='followed_by', to='profiles.Profile'),
),
]
diff --git a/conduit/apps/profiles/migrations/0003_profile_favorites.py b/conduit/apps/profiles/migrations/0003_profile_favorites.py
index 21ed426..57a22ae 100644
--- a/conduit/apps/profiles/migrations/0003_profile_favorites.py
+++ b/conduit/apps/profiles/migrations/0003_profile_favorites.py
@@ -16,6 +16,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='favorites',
- field=models.ManyToManyField(related_name='favorited_by', to='articles.Article'),
+ field=models.ManyToManyField(
+ related_name='favorited_by', to='articles.Article'),
),
]
diff --git a/conduit/apps/profiles/models.py b/conduit/apps/profiles/models.py
index 6c47aa8..4841f3a 100644
--- a/conduit/apps/profiles/models.py
+++ b/conduit/apps/profiles/models.py
@@ -37,7 +37,6 @@ class Profile(TimestampedModel):
related_name='favorited_by'
)
-
def __str__(self):
return self.user.username
diff --git a/conduit/apps/profiles/urls.py b/conduit/apps/profiles/urls.py
index 7ca5610..9d2ab6f 100644
--- a/conduit/apps/profiles/urls.py
+++ b/conduit/apps/profiles/urls.py
@@ -1,9 +1,9 @@
from django.conf.urls import url
-from .views import ProfileRetrieveAPIView, ProfileFollowAPIView
+from .views import ProfileFollowAPIView, ProfileRetrieveAPIView
urlpatterns = [
url(r'^profiles/(?P\w+)/?$', ProfileRetrieveAPIView.as_view()),
- url(r'^profiles/(?P\w+)/follow/?$',
+ url(r'^profiles/(?P\w+)/follow/?$',
ProfileFollowAPIView.as_view()),
]
diff --git a/dependencies/Pipfile b/dependencies/Pipfile
new file mode 100644
index 0000000..091a76e
--- /dev/null
+++ b/dependencies/Pipfile
@@ -0,0 +1,20 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+Django = "*"
+django-cors-middleware = "*"
+django-extensions = "*"
+djangorestframework = "*"
+PyJWT = "*"
+six = "*"
+
+[requires]
+python_version = "3.5"
+
+[pipenv]
+allow_prereleases = true
\ No newline at end of file
diff --git a/project-logo.png b/project-logo.png
index 38af45f..2fc614d 100644
Binary files a/project-logo.png and b/project-logo.png differ
diff --git a/requirements.txt b/requirements.txt
index 6eb9d54..83836d5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
-Django==1.10.5
+Django==1.11.29
django-cors-middleware==1.3.1
django-extensions==1.7.1
djangorestframework==3.4.4
PyJWT==1.4.2
six==1.10.0
+