Skip to content

Commit 9c305fc

Browse files
authored
Merge pull request #20 from JuanBenitezDev/refactor
Refactor views, using Django 3.1 and added filters
2 parents a604730 + 47a0f75 commit 9c305fc

25 files changed

+207
-213
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,6 @@ venv.bak/
106106

107107
# mypy
108108
.mypy_cache/
109+
110+
# Database
111+
db.sqlite3

README.md

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@
33

44
## Requirements
55
- Python 3.6
6-
- Django (2.1)
6+
- Django 3.1
77
- Django REST Framework
8-
- Django Rest Auth
98

109
## Installation
10+
After you cloned the repository, you want to create a virtual environment, so you have a clean python installation.
11+
You can do this by running the command
1112
```
12-
pip install django
13-
pip install djangorestframework
14-
pip install django-rest-auth
15-
pip install django-allauth
13+
python -m venv env
14+
```
15+
16+
After this, it is necessary to activate the virtual environment, you can get more information about this [here](https://docs.python.org/3/tutorial/venv.html)
17+
18+
You can install all the required dependencies by running
19+
```
20+
pip install -r requirements.txt
1621
```
1722

1823
## Structure
@@ -29,7 +34,9 @@ Endpoint |HTTP Method | CRUD Method | Result
2934
`movies/:id` | DELETE | DELETE | Delete a movie
3035

3136
## Use
32-
We can test the API using [curl](https://curl.haxx.se/) or [httpie](https://github.com/jakubroztocil/httpie#installation). Httpie is a user friendly http client that's written in Python. Let's install that.
37+
We can test the API using [curl](https://curl.haxx.se/) or [httpie](https://github.com/jakubroztocil/httpie#installation), or we can use [Postman](https://www.postman.com/)
38+
39+
Httpie is a user-friendly http client that's written in Python. Let's try and install that.
3340

3441
You can install httpie using pip:
3542
```
@@ -38,70 +45,105 @@ pip install httpie
3845

3946
First, we have to start up Django's development server.
4047
```
41-
python manage.py runserver
48+
python manage.py runserver
4249
```
4350
Only authenticated users can use the API services, for that reason if we try this:
4451
```
45-
http http://127.0.0.1:8000/api/v1/movies/3
52+
http http://127.0.0.1:8000/api/v1/movies/
4653
```
4754
we get:
4855
```
49-
{ "detail": "You must be authenticated" }
56+
{
57+
"detail": "Authentication credentials were not provided."
58+
}
5059
```
5160
Instead, if we try to access with credentials:
5261
```
53-
http http://127.0.0.1:8000/api/v1/movies/3 "Authorization: Token 7530ec9186a31a5b3dd8d03d84e34f80941391e3"
62+
http http://127.0.0.1:8000/api/v1/movies/3 "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjE2MjA4Mjk1LCJqdGkiOiI4NGNhZmMzMmFiZDA0MDQ2YjZhMzFhZjJjMmRiNjUyYyIsInVzZXJfaWQiOjJ9.NJrs-sXnghAwcMsIWyCvE2RuGcQ3Hiu5p3vBmLkHSvM"
5463
```
5564
we get the movie with id = 3
5665
```
5766
{ "title": "Avengers", "genre": "Superheroes", "year": 2012, "creator": "admin" }
5867
```
5968

60-
## Login and Tokens
69+
## Create users and Tokens
6170

62-
To get a token first we have to login
71+
First we need to create a user, so we can log in
72+
```
73+
http POST http://127.0.0.1:8000/api/v1/auth/register/ email="[email protected]" username="USERNAME" password1="PASSWORD" password2="PASSWORD"
6374
```
64-
http http://127.0.0.1:8000/rest-auth/login/ username="admin" password="root1234"
75+
76+
After we create an account we can use those credentials to get a token
77+
78+
To get a token first we need to request
79+
```
80+
http http://127.0.0.1:8000/api/v1/auth/token/ username="username" password="password"
6581
```
6682
after that, we get the token
6783
```
6884
{
69-
"key": "2d500db1e51153318e300860064e52c061e72016"
85+
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYxNjI5MjMyMSwianRpIjoiNGNkODA3YTlkMmMxNDA2NWFhMzNhYzMxOTgyMzhkZTgiLCJ1c2VyX2lkIjozfQ.hP1wPOPvaPo2DYTC9M1AuOSogdRL_mGP30CHsbpf4zA",
86+
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjE2MjA2MjIxLCJqdGkiOiJjNTNlNThmYjE4N2Q0YWY2YTE5MGNiMzhlNjU5ZmI0NSIsInVzZXJfaWQiOjN9.Csz-SgXoItUbT3RgB3zXhjA2DAv77hpYjqlgEMNAHps"
7087
}
7188
```
72-
**ALL request must be authenticated with a valid token, otherwise they will be invalid**
89+
We got two tokens, the access token will be used to authenticated all the requests we need to make, this access token will expire after some time.
90+
We can use the refresh token to request a need access token.
7391

74-
We can create new users. (password1 and password2 must be equal)
92+
requesting new access token
7593
```
76-
http POST http://127.0.0.1:8000/rest-auth/registration/ username="USERNAME" password1="PASSWORD" password2="PASSWORD"
94+
http http://127.0.0.1:8000/api/v1/auth/token/refresh/ refresh="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTYxNjI5MjMyMSwianRpIjoiNGNkODA3YTlkMmMxNDA2NWFhMzNhYzMxOTgyMzhkZTgiLCJ1c2VyX2lkIjozfQ.hP1wPOPvaPo2DYTC9M1AuOSogdRL_mGP30CHsbpf4zA"
7795
```
78-
And we can logout, the token must be your actual token
96+
and we will get a new access token
7997
```
80-
http POST http://127.0.0.1:8000/rest-auth/logout/ "Authorization: Token <YOUR_TOKEN>"
98+
{
99+
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjE2MjA4Mjk1LCJqdGkiOiI4NGNhZmMzMmFiZDA0MDQ2YjZhMzFhZjJjMmRiNjUyYyIsInVzZXJfaWQiOjJ9.NJrs-sXnghAwcMsIWyCvE2RuGcQ3Hiu5p3vBmLkHSvM"
100+
}
81101
```
82102

103+
83104
The API has some restrictions:
84105
- The movies are always associated with a creator (user who created it).
85106
- Only authenticated users may create and see movies.
86107
- Only the creator of a movie may update or delete it.
87-
- Unauthenticated requests shouldn't have access.
108+
- The API doesn't allow unauthenticated requests.
88109

89110
### Commands
90111
```
91-
http http://127.0.0.1:8000/api/v1/movies/ "Authorization: Token <YOUR_TOKEN>"
92-
http GET http://127.0.0.1:8000/api/v1/movies/3 "Authorization: Token <YOUR_TOKEN>"
93-
http POST http://127.0.0.1:8000/api/v1/movies/ "Authorization: Token <YOUR_TOKEN>" title="Ant Man and The Wasp" genre="Action" year=2018
94-
http PUT http://127.0.0.1:8000/api/v1/movies/3 "Authorization: Token <YOUR_TOKEN>" title="AntMan and The Wasp" genre="Action" year=2018
95-
http DELETE http://127.0.0.1:8000/api/v1/movies/3 "Authorization: Token <YOUR_TOKEN>"
112+
Get all movies
113+
http http://127.0.0.1:8000/api/v1/movies/ "Authorization: Bearer {YOUR_TOKEN}"
114+
Get a single movie
115+
http GET http://127.0.0.1:8000/api/v1/movies/{movie_id}/ "Authorization: Bearer {YOUR_TOKEN}"
116+
Create a new movie
117+
http POST http://127.0.0.1:8000/api/v1/movies/ "Authorization: Bearer {YOUR_TOKEN}" title="Ant Man and The Wasp" genre="Action" year=2018
118+
Full update a movie
119+
http PUT http://127.0.0.1:8000/api/v1/movies/{movie_id}/ "Authorization: Bearer {YOUR_TOKEN}" title="AntMan and The Wasp" genre="Action" year=2018
120+
Partial update a movie
121+
http PATCH http://127.0.0.1:8000/api/v1/movies/{movie_id}/ "Authorization: Bearer {YOUR_TOKEN}" title="AntMan and The Wasp"
122+
Delete a movie
123+
http DELETE http://127.0.0.1:8000/api/v1/movies/{movie_id}/ "Authorization: Bearer {YOUR_TOKEN}"
96124
```
97125

98126
### Pagination
99-
The API supports pagination, by default responses have a page_size=10 but if you want change that you can pass through params page=size=X
127+
The API supports pagination, by default responses have a page_size=10 but if you want change that you can pass through params page_size={your_page_size_number}
100128
```
101-
http http://127.0.0.1:8000/api/v1/movies/?page=1 "Authorization: Token <YOUR_TOKEN>"
102-
http http://127.0.0.1:8000/api/v1/movies/?page=3 "Authorization: Token <YOUR_TOKEN>"
103-
http http://127.0.0.1:8000/api/v1/movies/?page=3&page_size=15 "Authorization: Token <YOUR_TOKEN>"
129+
http http://127.0.0.1:8000/api/v1/movies/?page=1 "Authorization: Bearer {YOUR_TOKEN}"
130+
http http://127.0.0.1:8000/api/v1/movies/?page=3 "Authorization: Bearer {YOUR_TOKEN}"
131+
http http://127.0.0.1:8000/api/v1/movies/?page=3&page_size=15 "Authorization: Bearer {YOUR_TOKEN}"
104132
```
105133

106-
Finally, I provide a DB to make these tests.
134+
### Filters
135+
The API supports filtering, you can filter by the attributes of a movie like this
136+
```
137+
http http://127.0.0.1:8000/api/v1/movies/?title="AntMan" "Authorization: Bearer {YOUR_TOKEN}"
138+
http http://127.0.0.1:8000/api/v1/movies/?year=2020 "Authorization: Bearer {YOUR_TOKEN}"
139+
http http://127.0.0.1:8000/api/v1/movies/?year__gt=2019&year__lt=2022 "Authorization: Bearer {YOUR_TOKEN}"
140+
http http://127.0.0.1:8000/api/v1/movies/?genre="Action" "Authorization: Bearer {YOUR_TOKEN}"
141+
http http://127.0.0.1:8000/api/v1/movies/?creator__username="myUsername" "Authorization: Bearer {YOUR_TOKEN}"
142+
```
143+
144+
You can also combine multiples filters like so
145+
```
146+
http http://127.0.0.1:8000/api/v1/movies/?title="AntMan"&year=2020 "Authorization: Bearer {YOUR_TOKEN}"
147+
http http://127.0.0.1:8000/api/v1/movies/?year__gt=2019&year__lt=2022&genre="Action" "Authorization: Bearer {YOUR_TOKEN}"
148+
```
107149

api_crud/settings.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@
2929

3030
REST_FRAMEWORK = {
3131
'DEFAULT_AUTHENTICATION_CLASSES': (
32-
'rest_framework.authentication.TokenAuthentication',
33-
)
32+
'rest_framework_simplejwt.authentication.JWTAuthentication',
33+
),
34+
'DEFAULT_FILTER_BACKENDS': (
35+
'django_filters.rest_framework.DjangoFilterBackend',
36+
),
3437
}
3538

3639

@@ -44,13 +47,9 @@
4447
'django.contrib.messages',
4548
'django.contrib.staticfiles',
4649
'rest_framework',
50+
'django_filters',
51+
'authentication',
4752
'movies',
48-
'rest_framework.authtoken',
49-
'rest_auth',
50-
'django.contrib.sites',
51-
'allauth',
52-
'allauth.account',
53-
'rest_auth.registration',
5453
]
5554

5655
SITE_ID = 1

api_crud/urls.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11

22
from django.contrib import admin
3-
from django.conf.urls import include, url
4-
from .views import RegisterView, CustomLoginView
5-
3+
from django.urls import include, path
64

75
# urls
86
urlpatterns = [
9-
url(r'^', include('movies.urls')),
10-
url(r'^rest-auth/login/', CustomLoginView.as_view()),
11-
url(r'^rest-auth/registration/', RegisterView.as_view()),
12-
url(r'^rest-auth/', include('rest_auth.urls')),
13-
url(r'^admin/', admin.site.urls),
7+
path('api/v1/movies/', include('movies.urls')),
8+
path('api/v1/auth/', include('authentication.urls')),
9+
path('admin/', admin.site.urls),
1410
]

api_crud/views.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +0,0 @@
1-
from django.utils.decorators import method_decorator
2-
from django.views.decorators.debug import sensitive_post_parameters
3-
from rest_framework.response import Response
4-
from rest_framework.generics import CreateAPIView
5-
from rest_framework import status
6-
from allauth.account.utils import complete_signup
7-
from allauth.account import app_settings as allauth_settings
8-
from rest_auth.models import TokenModel
9-
from rest_auth.views import LoginView
10-
from rest_auth.registration.app_settings import RegisterSerializer, register_permission_classes
11-
from django.contrib.auth.models import User
12-
13-
sensitive_post_parameters_m = method_decorator(
14-
sensitive_post_parameters('password1', 'password2')
15-
)
16-
17-
18-
class RegisterView(CreateAPIView):
19-
serializer_class = RegisterSerializer
20-
permission_classes = register_permission_classes()
21-
token_model = TokenModel
22-
23-
@sensitive_post_parameters_m
24-
def dispatch(self, *args, **kwargs):
25-
return super(RegisterView, self).dispatch(*args, **kwargs)
26-
27-
def create(self, request, *args, **kwargs):
28-
serializer = self.get_serializer(data=request.data)
29-
serializer.is_valid(raise_exception=True)
30-
user = self.perform_create(serializer)
31-
headers = self.get_success_headers(serializer.data)
32-
content = {
33-
"details": "Registered"
34-
}
35-
return Response(content,
36-
status=status.HTTP_201_CREATED,
37-
headers=headers)
38-
39-
def perform_create(self, serializer):
40-
user = serializer.save(self.request)
41-
42-
complete_signup(self.request._request, user, None, None)
43-
return user
44-
45-
46-
class CustomLoginView(LoginView):
47-
48-
def get_response(self):
49-
orginal_response = super().get_response()
50-
51-
custom_response = {"user": {
52-
"username": self.user.username,
53-
"email": self.user.email
54-
}}
55-
56-
orginal_response.data.update(custom_response)
57-
return orginal_response

authentication/__init__.py

Whitespace-only changes.

authentication/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.contrib import admin
2+
3+
# Register your models here.

authentication/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class AuthConfig(AppConfig):
5+
name = 'authentication'

authentication/migrations/__init__.py

Whitespace-only changes.

authentication/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.db import models
2+
3+
# Create your models here.

0 commit comments

Comments
 (0)