From c8f51f35e9e3999e70d77b133ead7be35ffb5d40 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Fri, 26 Aug 2022 14:51:42 +0100 Subject: [PATCH 01/11] added crud how to document --- docs/crud.md | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 docs/crud.md diff --git a/docs/crud.md b/docs/crud.md new file mode 100644 index 0000000..aaa3423 --- /dev/null +++ b/docs/crud.md @@ -0,0 +1,320 @@ +# What is CRUD? + +The current state of web development almost always involves interaction with a database. That said, it is necessary to perform certain operations with it. Hence the need to use the 4 basic CRUD operations. +A great advantage of these operations is that with them alone you can create a complete web application. +### Crud Meaning +Meaning of CRUD: acronym referring to the 4 basic functions that are executed in a database model: + +##### Create: +- This function allows the application to create data and save it to the database. +##### Read: +- This function allows the application to read data from the database. +##### Update +- This function allows the application to update existing data in the database. +##### Delete +- This function allows the application to delete information from the database. + +# +# + + +# Flow (Backend) + +###### Assuming you already have an project created, let's skip the create project and app steps. +# + +##### Models (Example) +# +````python +class Movie(models.Model): + name = models.CharField(max_length=250) + director = models.CharField(max_length=100) + genre = models.CharField(max_length=200) + starring = models.CharField(max_length=350) + + def __str__(self): + return self.name +```` + + +##### Apply models to the DB +# +```sh +python manage.py makemigrations +``` +```sh +python manage.py migrate +``` +##### Create Super User +# +```sh +python manage.py createsuperuser +``` +##### Serializer +# +````python +from rest_framework import serializers +from backend_api.models import Movie + +class MovieSerializer(serializers.ModelSerializer): + class Meta: + model = Movie + fields = '__all__' +```` + +##### Create ViewSet +As seen in Django Rest Framework docs [(Link)](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset), the ModelViewSet class inherits from GenericAPIView and includes implementations for various actions, by mixing in the behavior of the various mixin classes. The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). + +````python +class MovieViewSet(viewsets.ModelViewSet): + serializer_class = MovieSerializer + queryset = Movie.objects.all() +```` + +##### Adding authentication permissions to views (Example) +REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access. [(Link)](https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/) + +````python +from rest_framework import permissions +```` +````python +class MovieViewSet(viewsets.ModelViewSet): + serializer_class = MovieSerializer + queryset = Movie.objects.all() + permission_classes = [permissions.IsAuthenticatedOrReadOnly] +```` + +##### Adding addicional validations to default ModelViewSetFunctions (Example) +This function calls the given model and get object from that if that object or model doesn't exist it raise 404 error. +````python +def retrieve(self, request, pk=None, *args, **kwargs): + instance = self.get_object_or_404(Movie, pk=pk) + serializer = self.get_serializer(instance) + return Response(serializer.data, status=status.HTTP_200_OK) +```` + +### Extra actions with validation (Example) + +Getting the Movie director by using an extra action with a different serializer +````python +class MovieDirectorSerializer(serializers.ModelSerializer): + class Meta: + model = Movie + fields = ('id', 'director') +```` + +````python +@action(detail=True, methods=["get"], url_path='movie-director') + def movie_director(self, request, pk=None): + movie = self.get_object_or_404(Movie, pk=pk) + serializer = self.get_serializer(movie) + return Response(serializer.data, status=status.HTTP_200_OK) +```` +Another example using actions: +````python +@action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf]) + def set_password(self, request, pk=None): + user = self.get_object_or_404(User, pk=pk) + serializer = PasswordSerializer(data=request.data) + if serializer.is_valid(): + user.set_password(serializer.validated_data['password']) + user.save() + return Response({'status': 'password set'}) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@action(detail=False) + def recent_users(self, request): + recent_users = User.objects.all().order_by('-last_login') + + page = self.paginate_queryset(recent_users) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(recent_users, many=True) + return Response(serializer.data) +```` + +# +# Flow (Frontend - Next.js) + +###### Again, assuming you already have an project created, let’s skip the create react app step. +# +# + +#### User Authentication [Source](https://nextjs.org/docs/authentication) +##### Authenticating Statically Generated Pages +Next.js automatically determines that a page is static if there are no blocking data requirements. This means the absence of getServerSideProps and getInitialProps in the page. Instead, your page can render a loading state from the server, followed by fetching the user client-side. +One advantage of this pattern is it allows pages to be served from a global CDN and preloaded using next/link. In practice, this results in a faster TTI (Time to Interactive). +Let's look at an example for a profile page. This will initially render a loading skeleton. Once the request for a user has finished, it will show the user's name: + +```sh +// pages/profile.js + +import useUser from '../lib/useUser' +import Layout from '../components/Layout' + +const Profile = () => { + // Fetch the user client-side + const { user } = useUser({ redirectTo: '/login' }) + + // Server-render loading state + if (!user || user.isLoggedIn === false) { + return Loading... + } + + // Once the user request finishes, show the user + return ( + +

Your Profile

+
{JSON.stringify(user, null, 2)}
+
+ ) +} + +export default Profile +``` +##### Authenticating Server-Rendered Pages (Example) +# +```sh +// pages/profile.js + +import withSession from '../lib/session' +import Layout from '../components/Layout' + +export const getServerSideProps = withSession(async function ({ req, res }) { + const { user } = req.session + + if (!user) { + return { + redirect: { + destination: '/login', + permanent: false, + }, + } + } + + return { + props: { user }, + } +}) + +const Profile = ({ user }) => { + // Show the user. No loading state is required + return ( + +

Your Profile

+
{JSON.stringify(user, null, 2)}
+
+ ) +} + +export default Profile +``` + + +#### Built-in Form validation (Example) [Source](https://nextjs.org/docs/guides/building-forms) + +```sh +
+ + + + + +
+``` + +With these validation checks in place, when a user tries to submit an empty field for Name, it gives an error that pops right in the form field. Similarly, a roll number can only be entered if it's 10-20 characters long. + + +#### JavaScript-based Form Validation (Example) +Form Validation is important to ensure that a user has submitted the correct data, in a correct format. JavaScript offers an additional level of validation along with HTML native form attributes on the client side. Developers generally prefer validating form data through JavaScript because its data processing is faster when compared to server-side validation, however front-end validation may be less secure in some scenarios as a malicious user could always send malformed data to your server. + +```sh + function validateFormWithJS() { + const name = document.querySelector('#name').value + const rollNumber = document.querySelector('#rollNumber').value + + if (!name) { + alert('Please enter your name.') + return false + } + + if (rollNumber.length < 3) { + toast('Roll Number should be at least 3 digits long.') + return false + } + } +``` + + +## Front-end - Back-end connection +#### Using the Axios API [Source](https://www.digitalocean.com/community/tutorials/react-axios-react) +Axios is promise-based, which gives you the ability to take advantage of JavaScript’s async and await for more readable asynchronous code. +```sh +npm install axios +``` + +### Examples Axios call +````sh +import axios from 'axios'; + async getMovies(): Promise { + try { + const {data} = await axios.get('http://127.0.0.1:8000/backend_api/movies'); + return data; + } catch (error) { + return { error }; + } +}; + +import axios from 'axios'; +export default axios.create({ + baseURL: "http://127.0.0.1:8000/backend_api/movies", + headers: { + 'Accept':'application/json', + 'Content-Type':'application/json', + } +}) +```` + +### Example Axios Post +````sh +async createMovie(body: { name: string; genre: string; starring: string }) { + try { + const {data} = await axios.post(`http://127.0.0.1:8000/backend_api/movies/`, body); + return data; + } catch (error) { + const err = error as AxiosError; + console.log('Error: ', err.message, err.response?.data); + return null; + } +} +```` +### Example Axios Put +````sh +const res = await axios.put(`http://127.0.0.1:8000/backend_api/movies/${movie.id}`, { + name: 'Movie edited', + genre: 'Comedy', + director: 'New director' +}); + + +async updateMovie(body: { name: 'Movie edited', genre: 'Comedy', director: 'New director'}) { + try { + const {data} = await axios.put(`http://127.0.0.1:8000/backend_api/movies/`, body); + return data; + } catch (error) { + const err = error as AxiosError; + console.log('Error: ', err.message, err.response?.data); + return null; + } +} From cdacd2ce9776141bc7e35825c535e51fdd88bbd3 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Mon, 29 Aug 2022 11:36:38 +0100 Subject: [PATCH 02/11] updates --- docs/crud.md | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 1 deletion(-) diff --git a/docs/crud.md b/docs/crud.md index aaa3423..39d3e41 100644 --- a/docs/crud.md +++ b/docs/crud.md @@ -50,7 +50,75 @@ python manage.py migrate ```sh python manage.py createsuperuser ``` -##### Serializer +## Authentication [Source](https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication) +Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted. + +#### How authentication is determined +The authentication schemes are always defined as a list of classes. REST framework will attempt to authenticate with each class in the list, and will set request.user and request.auth using the return value of the first class that successfully authenticates. + +If no class authenticates, request.user will be set to an instance of django.contrib.auth.models.AnonymousUser, and request.auth will be set to None. + +#### Setting the authentication scheme +```python +from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +class ExampleView(APIView): + authentication_classes = [SessionAuthentication, BasicAuthentication] + permission_classes = [IsAuthenticated] + + def get(self, request, format=None): + content = { + 'user': str(request.user), # `django.contrib.auth.User` instance. + 'auth': str(request.auth), # None + } + return Response(content) +``` + + +## Permissions [Source](https://www.django-rest-framework.org/api-guide/permissions/) +Together with authentication and throttling, permissions determine whether a request should be granted or denied access. + +Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the request.user and request.auth properties to determine if the incoming request should be permitted. + +Permissions are used to grant or deny access for different classes of users to different parts of the API. + +The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user. This corresponds to the IsAuthenticated class in REST framework. +```python +class IsAuthenticated(BasePermission): + """ + Allows access only to authenticated users. + """ + def has_permission(self, request, view): + return bool(request.user and request.user.is_authenticated) +``` +A slightly less strict style of permission would be to allow full access to authenticated users, but allow read-only access to unauthenticated users. This corresponds to the IsAuthenticatedOrReadOnly class in REST framework. +```python +class IsAuthenticatedOrReadOnly(BasePermission): + """ + The request is authenticated as a user, or is a read-only request. + """ + def has_permission(self, request, view): + return bool( + request.method in SAFE_METHODS or + request.user and + request.user.is_authenticated + ) +``` +#### How permissions are determined +Permissions in REST framework are always defined as a list of permission classes. + +Before running the main body of the view each permission in the list is checked. If any permission check fails, an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised, and the main body of the view will not run. + +When the permission checks fail, either a "403 Forbidden" or a "401 Unauthorized" response will be returned, according to the following rules: + +- The request was successfully authenticated, but permission was denied. — An HTTP 403 Forbidden response will be returned. +- The request was not successfully authenticated, and the highest priority authentication class does not use WWW-Authenticate headers. — An HTTP 403 Forbidden response will be returned. +- The request was not successfully authenticated, and the highest priority authentication class does use WWW-Authenticate headers. — An HTTP 401 Unauthorized response, with an appropriate WWW-Authenticate header will be returned. + +## Serializers # ````python from rest_framework import serializers @@ -61,7 +129,80 @@ class MovieSerializer(serializers.ModelSerializer): model = Movie fields = '__all__' ```` +# +#### Serializer Validations - [Source](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) +By default, all the model fields on the class will be mapped to a corresponding serializer fields. +Any relationships such as foreign keys on the model will be mapped to PrimaryKeyRelatedField. Reverse relationships are not included by default unless explicitly included as specified in the serializer relations documentation. + +When deserializing data, you always need to call is_valid() before attempting to access the validated data, or save an object instance. If any validation errors occur, the .errors property will contain a dictionary representing the resulting error messages. For example: +```python +serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'}) +serializer.is_valid() +# False +serializer.errors +# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']} +``` + +##### Inspecting a ModelSerializer +Serializer classes generate helpful verbose representation strings, that allow you to fully inspect the state of their fields. This is particularly useful when working with ModelSerializers where you want to determine what set of fields and validators are being automatically created for you. +Allow blank and null: +```python +name = CharField(allow_blank=True, max_length=100, required=False) +``` + +Regex validation: +```python +class UserSerializer(serializers.ModelSerializer): + first_name = serializers.RegexField(regex=r'^[a-zA-Z -.\'\_]+$', required=True) +``` +##### Field-level validation +Check that the blog post is about Django: +```python +from rest_framework import serializers + +class BlogPostSerializer(serializers.Serializer): + title = serializers.CharField(max_length=100) + content = serializers.CharField() + + def validate_title(self, value): + if 'django' not in value.lower(): + raise serializers.ValidationError("Blog post is not about Django") + return value +``` + +Check number limits: +```python +def validate_rating(self, value): + if value < 1 or value > 10: + raise serializers.ValidationError('Rating has to be between 1 and 10.') + return value +``` + +##### Object-level validation +To do any other validation that requires access to multiple fields, add a method called .validate() to your Serializer subclass. This method takes a single argument, which is a dictionary of field values. It should raise a serializers.ValidationError if necessary, or just return the validated values. For example: +```python +from rest_framework import serializers + +class EventSerializer(serializers.Serializer): + description = serializers.CharField(max_length=100) + start = serializers.DateTimeField() + finish = serializers.DateTimeField() + + def validate(self, data): + """ + Check that start is before finish. + """ + if data['start'] > data['finish']: + raise serializers.ValidationError("finish must occur after start") + return data +``` + + + + +# +# ##### Create ViewSet As seen in Django Rest Framework docs [(Link)](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset), the ModelViewSet class inherits from GenericAPIView and includes implementations for various actions, by mixing in the behavior of the various mixin classes. The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). @@ -136,6 +277,24 @@ Another example using actions: return Response(serializer.data) ```` +#### If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) + +```python +def check_positive_numbers(value1, value2): + if value1 <= 0 || value2 <= 0: + raise ValidationError( + _('The values must be greater than 0') + ) +``` +```python +def validate_birth_date(self, request): + birth_date = request.GET.get("birth_date", "") + if not birth_date: + return None + if birth_date >= datetime.date.today(): + raise ValidationError(_('Enter an accurate birthdate.')) + return date +``` # # Flow (Frontend - Next.js) From 85f0bc651430b80dff495ddf107c79f38bc64e74 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Mon, 29 Aug 2022 14:25:44 +0100 Subject: [PATCH 03/11] updates --- docs/crud.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/crud.md b/docs/crud.md index 39d3e41..5b83a32 100644 --- a/docs/crud.md +++ b/docs/crud.md @@ -59,6 +59,16 @@ The authentication schemes are always defined as a list of classes. REST framewo If no class authenticates, request.user will be set to an instance of django.contrib.auth.models.AnonymousUser, and request.auth will be set to None. #### Setting the authentication scheme +The default authentication schemes may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting. For example. + +```python +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.BasicAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ] +} +``` ```python from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.permissions import IsAuthenticated From 189496ac1bc3691bb4a5e0a3fa8a722223a8709d Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Mon, 29 Aug 2022 17:55:40 +0100 Subject: [PATCH 04/11] . --- docs/crud.md | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/docs/crud.md b/docs/crud.md index 5b83a32..bec2b51 100644 --- a/docs/crud.md +++ b/docs/crud.md @@ -128,6 +128,185 @@ When the permission checks fail, either a "403 Forbidden" or a "401 Unauthorized - The request was not successfully authenticated, and the highest priority authentication class does not use WWW-Authenticate headers. — An HTTP 403 Forbidden response will be returned. - The request was not successfully authenticated, and the highest priority authentication class does use WWW-Authenticate headers. — An HTTP 401 Unauthorized response, with an appropriate WWW-Authenticate header will be returned. +#### Custom Permissions (Example) +```python +class IsOwnerOrReadOnly(permissions.BasePermission): + """ + Object-level permission to only allow owners of an object to edit it. + Assumes the model instance has an `owner` attribute. + """ + + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request, + # so we'll always allow GET, HEAD or OPTIONS requests. + if request.method in permissions.SAFE_METHODS: + return True + + # Instance must have an attribute named `owner`. + return obj.owner == request.user +``` +Then, in the views file: +```python +permission_classes = [IsOwnerOrReadOnly] +``` +## Logging [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#logging) +Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems. +The Python standard library and Django already comes with an integrated logging module that provides basic as well as advanced logging features. Log messages can give helpful information about various events happening behind the scenes. + +A Python logging configuration consists of four parts: +- Loggers +- Handlers +- Filters +- Formatters + +##### Loggers +A logger is the entry point into the logging system. Each logger is a named bucket to which messages can be written for processing. +A logger is configured to have a log level. This log level describes the severity of the messages that the logger will handle. Python defines the following log levels: + +- DEBUG: Low level system information for debugging purposes +- INFO: General system information +- WARNING: Information describing a minor problem that has occurred. +- ERROR: Information describing a major problem that has occurred. +- CRITICAL: Information describing a critical problem that has occurred. + + +##### Handlers +The handler is the engine that determines what happens to each message in a logger. It describes a particular logging behavior, such as writing a message to the screen, to a file, or to a network socket. + +##### Filters +A filter is used to provide additional control over which log records are passed from logger to handler. +By default, any log message that meets log level requirements will be handled. However, by installing a filter, you can place additional criteria on the logging process. For example, you could install a filter that only allows ERROR messages from a particular source to be emitted. + +##### Formatters +Ultimately, a log record needs to be rendered as text. Formatters describe the exact format of that text. A formatter usually consists of a Python formatting string containing LogRecord attributes; however, you can also write custom formatters to implement specific formatting behavior. + +##### Default logging definition +Django’s default logging configuration inherits Python’s defaults. It’s available as django.utils.log.DEFAULT_LOGGING and defined in django/utils/log.py: +```python +{ + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', + }, + 'require_debug_true': { + '()': 'django.utils.log.RequireDebugTrue', + }, + }, + 'formatters': { + 'django.server': { + '()': 'django.utils.log.ServerFormatter', + 'format': '[{server_time}] {message}', + 'style': '{', + } + }, + 'handlers': { + 'console': { + 'level': 'INFO', + 'filters': ['require_debug_true'], + 'class': 'logging.StreamHandler', + }, + 'django.server': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'django.server', + }, + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django': { + 'handlers': ['console', 'mail_admins'], + 'level': 'INFO', + }, + 'django.server': { + 'handlers': ['django.server'], + 'level': 'INFO', + 'propagate': False, + }, + } +} +``` + +##### Basic Custom Logging Configuration [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#topic-logging-parts-loggers) +# +Logging configuration that allows to output warnings in the console: +```python +import os + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + }, + }, + 'root': { + 'handlers': ['console'], + 'level': 'WARNING', + }, +} +``` +You don’t have to log to the console. Here’s a configuration which writes all logging from the django named logger to a local file: +```python +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': '/path/to/django/debug.log', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + }, + }, +} +``` + + +### Different types of Logging +##### Access logs +# +```python +import datetime +import logging +logger = logging.getLogger(__name__) + +def home(request): + logger.warning('Home page was accessed at '+str(datetime.datetime.now())+' hours') +``` + + +##### Audit logs [Source](https://django-auditlog.readthedocs.io/en/latest/usage.html) + +Auditlog can automatically log changes to objects for you. This functionality is based on Django’s signals, but linking your models to Auditlog is even easier than using signals. +Registering your model for logging can be done with a single line of code, as the following example illustrates: +```python +from django.db import models + +from auditlog.models import AuditlogHistoryField +from auditlog.registry import auditlog + +class MyModel(models.Model): + sku = models.CharField(max_length=20) + version = models.CharField(max_length=5) + product = models.CharField(max_length=50, verbose_name='Product Name') + history = AuditlogHistoryField() + +auditlog.register(MyModel) +``` +# ## Serializers # ````python @@ -208,6 +387,17 @@ class EventSerializer(serializers.Serializer): return data ``` +##### Custom Validator (Example) +Individual fields on a serializer can include validators, by declaring them on the field instance, for example: +```python +def multiple_of_ten(value): + if value % 10 != 0: + raise serializers.ValidationError('Not a multiple of ten') + +class GameRecord(serializers.Serializer): + score = IntegerField(validators=[multiple_of_ten]) +``` + From ff06bb3d88aaa835ce3d1a76c10741dedc16f741 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Tue, 30 Aug 2022 16:10:22 +0100 Subject: [PATCH 05/11] . --- docs/crud.md | 197 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 185 insertions(+), 12 deletions(-) diff --git a/docs/crud.md b/docs/crud.md index bec2b51..715449a 100644 --- a/docs/crud.md +++ b/docs/crud.md @@ -149,6 +149,26 @@ Then, in the views file: ```python permission_classes = [IsOwnerOrReadOnly] ``` + +#### Custom Model Permissions + +To create custom permissions for a given model object, use the permissions model Meta attribute. + +This example Task model creates two custom permissions, i.e., actions users can or cannot do with Task instances, specific to your application: + +```python +class Task(models.Model): + ... + class Meta: + permissions = [ + ("change_task_status", "Can change the status of tasks"), + ("close_task", "Can remove a task by setting its status as closed"), + ] +``` +The only thing this does is create those extra permissions when you run manage.py migrate (the function that creates permissions is connected to the post_migrate signal). Your code is in charge of checking the value of these permissions when a user is trying to access the functionality provided by the application (changing the status of tasks or closing tasks.) Continuing the above example, the following checks if a user may close tasks: +```python +user.has_perm('app.close_task') +``` ## Logging [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#logging) Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems. The Python standard library and Django already comes with an integrated logging module that provides basic as well as advanced logging features. Log messages can give helpful information about various events happening behind the scenes. @@ -276,6 +296,16 @@ LOGGING = { ### Different types of Logging +##### Basic add instance log +# +```python +import logging,traceback +logger = logging.getLogger('django') + +def addSomeInstance(request): + //View logic + logger.warning('Added instance!') +``` ##### Access logs # ```python @@ -287,8 +317,10 @@ def home(request): logger.warning('Home page was accessed at '+str(datetime.datetime.now())+' hours') ``` - -##### Audit logs [Source](https://django-auditlog.readthedocs.io/en/latest/usage.html) +## Audit logs +Audit logs is the process of keeping track of the activity within some piece of software. It logs the event, the time which it happened and the responsible. +There are several django libraries that offers this feature, one of those is the django-auditlog. +### django-auditlog [Source](https://django-auditlog.readthedocs.io/en/latest/usage.html) Auditlog can automatically log changes to objects for you. This functionality is based on Django’s signals, but linking your models to Auditlog is even easier than using signals. Registering your model for logging can be done with a single line of code, as the following example illustrates: @@ -306,6 +338,8 @@ class MyModel(models.Model): auditlog.register(MyModel) ``` + + # ## Serializers # @@ -401,9 +435,9 @@ class GameRecord(serializers.Serializer): + # -# -##### Create ViewSet +# Create ViewSet As seen in Django Rest Framework docs [(Link)](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset), the ModelViewSet class inherits from GenericAPIView and includes implementations for various actions, by mixing in the behavior of the various mixin classes. The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). ````python @@ -412,7 +446,7 @@ class MovieViewSet(viewsets.ModelViewSet): queryset = Movie.objects.all() ```` -##### Adding authentication permissions to views (Example) +### Adding authentication permissions to views (Example) REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access. [(Link)](https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/) ````python @@ -425,7 +459,21 @@ class MovieViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticatedOrReadOnly] ```` -##### Adding addicional validations to default ModelViewSetFunctions (Example) +### Different permissions for different actions +You may inspect these attributes to adjust behaviour based on the current action. For example, you could restrict permissions to everything except the list action similar to this: +```python +def get_permissions(self): + """ + Instantiates and returns the list of permissions that this view requires. + """ + if self.action == 'list': + permission_classes = [IsAuthenticated] + else: + permission_classes = [IsAdminUser] + return [permission() for permission in permission_classes] +``` + +### Adding addicional validations to default ModelViewSetFunctions (Example) This function calls the given model and get object from that if that object or model doesn't exist it raise 404 error. ````python def retrieve(self, request, pk=None, *args, **kwargs): @@ -476,7 +524,7 @@ Another example using actions: serializer = self.get_serializer(recent_users, many=True) return Response(serializer.data) ```` - + #### If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) ```python @@ -499,7 +547,6 @@ def validate_birth_date(self, request): # Flow (Frontend - Next.js) ###### Again, assuming you already have an project created, let’s skip the create react app step. -# # #### User Authentication [Source](https://nextjs.org/docs/authentication) @@ -598,7 +645,7 @@ With these validation checks in place, when a user tries to submit an empty fiel #### JavaScript-based Form Validation (Example) Form Validation is important to ensure that a user has submitted the correct data, in a correct format. JavaScript offers an additional level of validation along with HTML native form attributes on the client side. Developers generally prefer validating form data through JavaScript because its data processing is faster when compared to server-side validation, however front-end validation may be less secure in some scenarios as a malicious user could always send malformed data to your server. -```sh +```tsx function validateFormWithJS() { const name = document.querySelector('#name').value const rollNumber = document.querySelector('#rollNumber').value @@ -614,8 +661,84 @@ Form Validation is important to ensure that a user has submitted the correct dat } } ``` +## Creating TypeScript types in Next.js [Source](https://blog.logrocket.com/using-next-js-with-typescript/) + +You can create types for anything in your application, including prop types, API responses, arguments for your utility functions, and even properties of your global state. + +The interface below reflects the shape of a Post object. It expects id, title, and body properties. +```tsx +// types/index.ts + +export interface IPost { + id: number + title: string + body: string +} +``` + +```tsx +// components/AddPost.tsx + +import * as React from 'react' +import { IPost } from '../types' + +type Props = { + savePost: (e: React.FormEvent, formData: IPost) => void +} + +const AddPost: React.FC = ({ savePost }) => { + const [formData, setFormData] = React.useState() + + const handleForm = (e: React.FormEvent): void => { + setFormData({ + ...formData, + [e.currentTarget.id]: e.currentTarget.value, + }) + } + + return ( +
savePost(e, formData)}> +
+
+ + +
+
+ + +
+
+ +
+ ) +} + +export default AddPost +``` + +### Response Types +Another thing you might be using is the API routes from Next.js. +```tsx +export default function handler(req, res) { + res.status(200).json({ name: 'John Doe' }); +} +``` +We can typecast the req and res to be types like this: +```tsx +import { NextApiRequest, NextApiResponse } from 'next'; +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }); +} +``` + +# ## Front-end - Back-end connection #### Using the Axios API [Source](https://www.digitalocean.com/community/tutorials/react-axios-react) Axios is promise-based, which gives you the ability to take advantage of JavaScript’s async and await for more readable asynchronous code. @@ -624,7 +747,7 @@ npm install axios ``` ### Examples Axios call -````sh +````tsx import axios from 'axios'; async getMovies(): Promise { try { @@ -646,7 +769,7 @@ export default axios.create({ ```` ### Example Axios Post -````sh +````tsx async createMovie(body: { name: string; genre: string; starring: string }) { try { const {data} = await axios.post(`http://127.0.0.1:8000/backend_api/movies/`, body); @@ -659,7 +782,7 @@ async createMovie(body: { name: string; genre: string; starring: string }) { } ```` ### Example Axios Put -````sh +````tsx const res = await axios.put(`http://127.0.0.1:8000/backend_api/movies/${movie.id}`, { name: 'Movie edited', genre: 'Comedy', @@ -677,3 +800,53 @@ async updateMovie(body: { name: 'Movie edited', genre: 'Comedy', director: 'New return null; } } +```` + +### Making Http GET requests with Axios in TypeScript +```tsx +// servies/types + +type User = { + id: number; + email: string; + first_name: string; +}; + +type GetUsersResponse = { + data: User[]; +}; +``` + +```tsx +import axios from 'axios'; +import { User, GetUsersResponse } from '../services/types' + +async function getUsers() { + try { + const { data, status } = await axios.get( + 'https://reqres.in/api/users', + { + headers: { + Accept: 'application/json', + }, + }, + ); + + console.log(JSON.stringify(data, null, 4)); + + console.log('response status is: ', status); + + return data; + } catch (error) { + if (axios.isAxiosError(error)) { + console.log('error message: ', error.message); + return error.message; + } else { + console.log('unexpected error: ', error); + return 'An unexpected error occurred'; + } + } +} + +getUsers(); +``` \ No newline at end of file From fcd3e623526e9d98e63f13916fb8591c4ed9b0c4 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Wed, 31 Aug 2022 14:40:51 +0100 Subject: [PATCH 06/11] final updates --- docs/crud/0-index.md | 25 ++ docs/crud/01-checklist.md | 33 +++ docs/{crud.md => crud/1-backend.md} | 351 ++-------------------------- docs/crud/2-frontend.md | 328 ++++++++++++++++++++++++++ 4 files changed, 401 insertions(+), 336 deletions(-) create mode 100644 docs/crud/0-index.md create mode 100644 docs/crud/01-checklist.md rename docs/{crud.md => crud/1-backend.md} (68%) create mode 100644 docs/crud/2-frontend.md diff --git a/docs/crud/0-index.md b/docs/crud/0-index.md new file mode 100644 index 0000000..ee3a8c0 --- /dev/null +++ b/docs/crud/0-index.md @@ -0,0 +1,25 @@ +# What is CRUD? + +The current state of web development almost always involves interaction with a database. That said, it is necessary to perform certain operations with it. Hence the need to use the 4 basic CRUD operations. +A great advantage of these operations is that with them alone you can create a complete web application. +### Crud Meaning +Meaning of CRUD: acronym referring to the 4 basic functions that are executed in a database model: + +##### Create: +- This function allows the application to create data and save it to the database. +##### Read: +- This function allows the application to read data from the database. +##### Update +- This function allows the application to update existing data in the database. +##### Delete +- This function allows the application to delete information from the database. +# + + +## Backend flow and steps +[Flow - Backend](1-backend.md) +## Frontend flow and steps +[Flow - FrontEnd](2-frontend.md) + + + diff --git a/docs/crud/01-checklist.md b/docs/crud/01-checklist.md new file mode 100644 index 0000000..6012a65 --- /dev/null +++ b/docs/crud/01-checklist.md @@ -0,0 +1,33 @@ +# Checkllist + +## Steps to keep in mind when developing a CRUD app + + + +### Backend: + +- [Models and DB](1-backend.md#models) + +- [Authentication](1-backend.md#authentication) + +- [Permissions](1-backend.md#permissions) + +- [Logging](1-backend.md#logging) + +- [Serializers](1-backend.md#serializers) + +- [Views](1-backend.md#views) + + + +### Frontend: + +- [Authentication](2-frontend.md#authentication) + +- [Validation](2-frontend.md#validation) + +- [TypeScript](2-frontend.md#typescript) + +- [Responses](2-frontend.md#responses) + +- [Axios](2-frontend.md#axios) \ No newline at end of file diff --git a/docs/crud.md b/docs/crud/1-backend.md similarity index 68% rename from docs/crud.md rename to docs/crud/1-backend.md index 715449a..9bcbe5d 100644 --- a/docs/crud.md +++ b/docs/crud/1-backend.md @@ -1,29 +1,10 @@ -# What is CRUD? - -The current state of web development almost always involves interaction with a database. That said, it is necessary to perform certain operations with it. Hence the need to use the 4 basic CRUD operations. -A great advantage of these operations is that with them alone you can create a complete web application. -### Crud Meaning -Meaning of CRUD: acronym referring to the 4 basic functions that are executed in a database model: - -##### Create: -- This function allows the application to create data and save it to the database. -##### Read: -- This function allows the application to read data from the database. -##### Update -- This function allows the application to update existing data in the database. -##### Delete -- This function allows the application to delete information from the database. - -# -# - - +[Flow - Backend](0-index.md) # Flow (Backend) -###### Assuming you already have an project created, let's skip the create project and app steps. +### Assuming you already have an project created, let's skip the create project and app steps. # -##### Models (Example) +# Models (Example) # ````python class Movie(models.Model): @@ -50,7 +31,9 @@ python manage.py migrate ```sh python manage.py createsuperuser ``` -## Authentication [Source](https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication) +# Authentication +[Source](https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication) + Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted. #### How authentication is determined @@ -88,7 +71,9 @@ class ExampleView(APIView): ``` -## Permissions [Source](https://www.django-rest-framework.org/api-guide/permissions/) +# Permissions +[Source](https://www.django-rest-framework.org/api-guide/permissions/) + Together with authentication and throttling, permissions determine whether a request should be granted or denied access. Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the request.user and request.auth properties to determine if the incoming request should be permitted. @@ -169,7 +154,8 @@ The only thing this does is create those extra permissions when you run manage.p ```python user.has_perm('app.close_task') ``` -## Logging [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#logging) +# Logging +# [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#logging) Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems. The Python standard library and Django already comes with an integrated logging module that provides basic as well as advanced logging features. Log messages can give helpful information about various events happening behind the scenes. @@ -341,7 +327,7 @@ auditlog.register(MyModel) # -## Serializers +# Serializers # ````python from rest_framework import serializers @@ -436,8 +422,8 @@ class GameRecord(serializers.Serializer): -# -# Create ViewSet + +# Views As seen in Django Rest Framework docs [(Link)](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset), the ModelViewSet class inherits from GenericAPIView and includes implementations for various actions, by mixing in the behavior of the various mixin classes. The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). ````python @@ -524,7 +510,7 @@ Another example using actions: serializer = self.get_serializer(recent_users, many=True) return Response(serializer.data) ```` - + #### If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) ```python @@ -542,311 +528,4 @@ def validate_birth_date(self, request): if birth_date >= datetime.date.today(): raise ValidationError(_('Enter an accurate birthdate.')) return date -``` -# -# Flow (Frontend - Next.js) - -###### Again, assuming you already have an project created, let’s skip the create react app step. -# - -#### User Authentication [Source](https://nextjs.org/docs/authentication) -##### Authenticating Statically Generated Pages -Next.js automatically determines that a page is static if there are no blocking data requirements. This means the absence of getServerSideProps and getInitialProps in the page. Instead, your page can render a loading state from the server, followed by fetching the user client-side. -One advantage of this pattern is it allows pages to be served from a global CDN and preloaded using next/link. In practice, this results in a faster TTI (Time to Interactive). -Let's look at an example for a profile page. This will initially render a loading skeleton. Once the request for a user has finished, it will show the user's name: - -```sh -// pages/profile.js - -import useUser from '../lib/useUser' -import Layout from '../components/Layout' - -const Profile = () => { - // Fetch the user client-side - const { user } = useUser({ redirectTo: '/login' }) - - // Server-render loading state - if (!user || user.isLoggedIn === false) { - return Loading... - } - - // Once the user request finishes, show the user - return ( - -

Your Profile

-
{JSON.stringify(user, null, 2)}
-
- ) -} - -export default Profile -``` -##### Authenticating Server-Rendered Pages (Example) -# -```sh -// pages/profile.js - -import withSession from '../lib/session' -import Layout from '../components/Layout' - -export const getServerSideProps = withSession(async function ({ req, res }) { - const { user } = req.session - - if (!user) { - return { - redirect: { - destination: '/login', - permanent: false, - }, - } - } - - return { - props: { user }, - } -}) - -const Profile = ({ user }) => { - // Show the user. No loading state is required - return ( - -

Your Profile

-
{JSON.stringify(user, null, 2)}
-
- ) -} - -export default Profile -``` - - -#### Built-in Form validation (Example) [Source](https://nextjs.org/docs/guides/building-forms) - -```sh -
- - - - - -
-``` - -With these validation checks in place, when a user tries to submit an empty field for Name, it gives an error that pops right in the form field. Similarly, a roll number can only be entered if it's 10-20 characters long. - - -#### JavaScript-based Form Validation (Example) -Form Validation is important to ensure that a user has submitted the correct data, in a correct format. JavaScript offers an additional level of validation along with HTML native form attributes on the client side. Developers generally prefer validating form data through JavaScript because its data processing is faster when compared to server-side validation, however front-end validation may be less secure in some scenarios as a malicious user could always send malformed data to your server. - -```tsx - function validateFormWithJS() { - const name = document.querySelector('#name').value - const rollNumber = document.querySelector('#rollNumber').value - - if (!name) { - alert('Please enter your name.') - return false - } - - if (rollNumber.length < 3) { - toast('Roll Number should be at least 3 digits long.') - return false - } - } -``` -## Creating TypeScript types in Next.js [Source](https://blog.logrocket.com/using-next-js-with-typescript/) - -You can create types for anything in your application, including prop types, API responses, arguments for your utility functions, and even properties of your global state. - -The interface below reflects the shape of a Post object. It expects id, title, and body properties. -```tsx -// types/index.ts - -export interface IPost { - id: number - title: string - body: string -} -``` - -```tsx -// components/AddPost.tsx - -import * as React from 'react' -import { IPost } from '../types' - -type Props = { - savePost: (e: React.FormEvent, formData: IPost) => void -} - -const AddPost: React.FC = ({ savePost }) => { - const [formData, setFormData] = React.useState() - - const handleForm = (e: React.FormEvent): void => { - setFormData({ - ...formData, - [e.currentTarget.id]: e.currentTarget.value, - }) - } - - return ( -
savePost(e, formData)}> -
-
- - -
-
- - -
-
- -
- ) -} - -export default AddPost -``` - -### Response Types -Another thing you might be using is the API routes from Next.js. -```tsx -export default function handler(req, res) { - res.status(200).json({ name: 'John Doe' }); -} -``` -We can typecast the req and res to be types like this: -```tsx -import { NextApiRequest, NextApiResponse } from 'next'; - -export default function handler(req: NextApiRequest, res: NextApiResponse) { - res.status(200).json({ name: 'John Doe' }); -} -``` - - -# -## Front-end - Back-end connection -#### Using the Axios API [Source](https://www.digitalocean.com/community/tutorials/react-axios-react) -Axios is promise-based, which gives you the ability to take advantage of JavaScript’s async and await for more readable asynchronous code. -```sh -npm install axios -``` - -### Examples Axios call -````tsx -import axios from 'axios'; - async getMovies(): Promise { - try { - const {data} = await axios.get('http://127.0.0.1:8000/backend_api/movies'); - return data; - } catch (error) { - return { error }; - } -}; - -import axios from 'axios'; -export default axios.create({ - baseURL: "http://127.0.0.1:8000/backend_api/movies", - headers: { - 'Accept':'application/json', - 'Content-Type':'application/json', - } -}) -```` - -### Example Axios Post -````tsx -async createMovie(body: { name: string; genre: string; starring: string }) { - try { - const {data} = await axios.post(`http://127.0.0.1:8000/backend_api/movies/`, body); - return data; - } catch (error) { - const err = error as AxiosError; - console.log('Error: ', err.message, err.response?.data); - return null; - } -} -```` -### Example Axios Put -````tsx -const res = await axios.put(`http://127.0.0.1:8000/backend_api/movies/${movie.id}`, { - name: 'Movie edited', - genre: 'Comedy', - director: 'New director' -}); - - -async updateMovie(body: { name: 'Movie edited', genre: 'Comedy', director: 'New director'}) { - try { - const {data} = await axios.put(`http://127.0.0.1:8000/backend_api/movies/`, body); - return data; - } catch (error) { - const err = error as AxiosError; - console.log('Error: ', err.message, err.response?.data); - return null; - } -} -```` - -### Making Http GET requests with Axios in TypeScript -```tsx -// servies/types - -type User = { - id: number; - email: string; - first_name: string; -}; - -type GetUsersResponse = { - data: User[]; -}; -``` - -```tsx -import axios from 'axios'; -import { User, GetUsersResponse } from '../services/types' - -async function getUsers() { - try { - const { data, status } = await axios.get( - 'https://reqres.in/api/users', - { - headers: { - Accept: 'application/json', - }, - }, - ); - - console.log(JSON.stringify(data, null, 4)); - - console.log('response status is: ', status); - - return data; - } catch (error) { - if (axios.isAxiosError(error)) { - console.log('error message: ', error.message); - return error.message; - } else { - console.log('unexpected error: ', error); - return 'An unexpected error occurred'; - } - } -} - -getUsers(); ``` \ No newline at end of file diff --git a/docs/crud/2-frontend.md b/docs/crud/2-frontend.md new file mode 100644 index 0000000..496239b --- /dev/null +++ b/docs/crud/2-frontend.md @@ -0,0 +1,328 @@ +[Index](0-index.md) +# Flow (Frontend - Next.js) + +#### Again, assuming you already have an project created, let’s skip the create react app step. +# + +# Authentication +[Source](https://nextjs.org/docs/authentication) +## Authenticating Statically Generated Pages +Next.js automatically determines that a page is static if there are no blocking data requirements. This means the absence of getServerSideProps and getInitialProps in the page. Instead, your page can render a loading state from the server, followed by fetching the user client-side. +One advantage of this pattern is it allows pages to be served from a global CDN and preloaded using next/link. In practice, this results in a faster TTI (Time to Interactive). +Let's look at an example for a profile page. This will initially render a loading skeleton. Once the request for a user has finished, it will show the user's name: + +```sh +// pages/profile.js + +import useUser from '../lib/useUser' +import Layout from '../components/Layout' + +const Profile = () => { + // Fetch the user client-side + const { user } = useUser({ redirectTo: '/login' }) + + // Server-render loading state + if (!user || user.isLoggedIn === false) { + return Loading... + } + + // Once the user request finishes, show the user + return ( + +

Your Profile

+
{JSON.stringify(user, null, 2)}
+
+ ) +} + +export default Profile +``` +##### Authenticating Server-Rendered Pages (Example) +# +```sh +// pages/profile.js + +import withSession from '../lib/session' +import Layout from '../components/Layout' + +export const getServerSideProps = withSession(async function ({ req, res }) { + const { user } = req.session + + if (!user) { + return { + redirect: { + destination: '/login', + permanent: false, + }, + } + } + + return { + props: { user }, + } +}) + +const Profile = ({ user }) => { + // Show the user. No loading state is required + return ( + +

Your Profile

+
{JSON.stringify(user, null, 2)}
+
+ ) +} + +export default Profile +``` + +# Validation +#### Built-in Form validation (Example) [Source](https://nextjs.org/docs/guides/building-forms) + +```sh +
+ + + + + +
+``` + +With these validation checks in place, when a user tries to submit an empty field for Name, it gives an error that pops right in the form field. Similarly, a roll number can only be entered if it's 10-20 characters long. + + +## JavaScript-based Form Validation (Example) +Form Validation is important to ensure that a user has submitted the correct data, in a correct format. JavaScript offers an additional level of validation along with HTML native form attributes on the client side. Developers generally prefer validating form data through JavaScript because its data processing is faster when compared to server-side validation, however front-end validation may be less secure in some scenarios as a malicious user could always send malformed data to your server. + +```tsx + function validateFormWithJS() { + const name = document.querySelector('#name').value + const rollNumber = document.querySelector('#rollNumber').value + + if (!name) { + alert('Please enter your name.') + return false + } + + if (rollNumber.length < 3) { + toast('Roll Number should be at least 3 digits long.') + return false + } + } +``` +# TypeScript +## Creating TypeScript types in Next.js [Source](https://blog.logrocket.com/using-next-js-with-typescript/) + +You can create types for anything in your application, including prop types, API responses, arguments for your utility functions, and even properties of your global state. + +The interface below reflects the shape of a Post object. It expects id, title, and body properties. +```tsx +// types/index.ts + +export interface IPost { + id: number + title: string + body: string +} +``` + +```tsx +// components/AddPost.tsx + +import * as React from 'react' +import { IPost } from '../types' + +type Props = { + savePost: (e: React.FormEvent, formData: IPost) => void +} + +const AddPost: React.FC = ({ savePost }) => { + const [formData, setFormData] = React.useState() + + const handleForm = (e: React.FormEvent): void => { + setFormData({ + ...formData, + [e.currentTarget.id]: e.currentTarget.value, + }) + } + + return ( +
savePost(e, formData)}> +
+
+ + +
+
+ + +
+
+ +
+ ) +} + +export default AddPost +``` + +# Responses +## Response Types +Another thing you might be using is the API routes from Next.js. +```tsx +export default function handler(req, res) { + res.status(200).json({ name: 'John Doe' }); +} +``` +We can typecast the req and res to be types like this: +```tsx +import { NextApiRequest, NextApiResponse } from 'next'; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }); +} +``` + + +# Axios +## Front-end - Back-end connection +#### Using the Axios API [Source](https://www.digitalocean.com/community/tutorials/react-axios-react) +Axios is promise-based, which gives you the ability to take advantage of JavaScript’s async and await for more readable asynchronous code. +```sh +npm install axios +``` + + +### Response type conversion + +Sometimes the data coming from the API has values that may break our application. Due to this fact you should always parse the response. For example: +```js + baseUrl = "https://jsonplaceholder.typicode.com"; + async getUser(id){ + try{ + const req = await fetch(`${this.baseUrl}/users/${id}`); + this._handleRequest(req); + const user = await req.json(); + return { email: user.email, name: user.name } + } catch(e){ + throw e; + } + } +``` + +### Examples Axios call +````tsx +import axios from 'axios'; + async getMovies(): Promise { + try { + const {data} = await axios.get('http://127.0.0.1:8000/backend_api/movies'); + return data; + } catch (error) { + return { error }; + } +}; + +import axios from 'axios'; +export default axios.create({ + baseURL: "http://127.0.0.1:8000/backend_api/movies", + headers: { + 'Accept':'application/json', + 'Content-Type':'application/json', + } +}) +```` + +### Example Axios Post +````tsx +async createMovie(body: { name: string; genre: string; starring: string }) { + try { + const {data} = await axios.post(`http://127.0.0.1:8000/backend_api/movies/`, body); + return data; + } catch (error) { + const err = error as AxiosError; + console.log('Error: ', err.message, err.response?.data); + return null; + } +} +```` +### Example Axios Put +````tsx +const res = await axios.put(`http://127.0.0.1:8000/backend_api/movies/${movie.id}`, { + name: 'Movie edited', + genre: 'Comedy', + director: 'New director' +}); + + +async updateMovie(body: { name: 'Movie edited', genre: 'Comedy', director: 'New director'}) { + try { + const {data} = await axios.put(`http://127.0.0.1:8000/backend_api/movies/`, body); + return data; + } catch (error) { + const err = error as AxiosError; + console.log('Error: ', err.message, err.response?.data); + return null; + } +} +```` + +### Making Http GET requests with Axios in TypeScript +```tsx +// servies/types + +type User = { + id: number; + email: string; + first_name: string; +}; + +type GetUsersResponse = { + data: User[]; +}; +``` + +```tsx +import axios from 'axios'; +import { User, GetUsersResponse } from '../services/types' + +async function getUsers() { + try { + const { data, status } = await axios.get( + 'https://reqres.in/api/users', + { + headers: { + Accept: 'application/json', + }, + }, + ); + + console.log(JSON.stringify(data, null, 4)); + + console.log('response status is: ', status); + + return data; + } catch (error) { + if (axios.isAxiosError(error)) { + console.log('error message: ', error.message); + return error.message; + } else { + console.log('unexpected error: ', error); + return 'An unexpected error occurred'; + } + } +} + +getUsers(); +``` \ No newline at end of file From 001e5fa6251069c1d61e89471e537f8dc5cdd793 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Wed, 31 Aug 2022 14:52:08 +0100 Subject: [PATCH 07/11] . --- docs/crud/01-checklist.md | 2 +- docs/crud/1-backend.md | 58 +++++++++++++++++++++------------------ docs/crud/2-frontend.md | 22 ++++++++------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/docs/crud/01-checklist.md b/docs/crud/01-checklist.md index 6012a65..42ea612 100644 --- a/docs/crud/01-checklist.md +++ b/docs/crud/01-checklist.md @@ -1,4 +1,4 @@ -# Checkllist +# Development checkllist ## Steps to keep in mind when developing a CRUD app diff --git a/docs/crud/1-backend.md b/docs/crud/1-backend.md index 9bcbe5d..d514e5f 100644 --- a/docs/crud/1-backend.md +++ b/docs/crud/1-backend.md @@ -4,8 +4,9 @@ ### Assuming you already have an project created, let's skip the create project and app steps. # -# Models (Example) -# +# Models +Example + ````python class Movie(models.Model): name = models.CharField(max_length=250) @@ -18,7 +19,7 @@ class Movie(models.Model): ```` -##### Apply models to the DB +### Apply models to the DB # ```sh python manage.py makemigrations @@ -26,7 +27,7 @@ python manage.py makemigrations ```sh python manage.py migrate ``` -##### Create Super User +### Create Super User # ```sh python manage.py createsuperuser @@ -41,7 +42,8 @@ The authentication schemes are always defined as a list of classes. REST framewo If no class authenticates, request.user will be set to an instance of django.contrib.auth.models.AnonymousUser, and request.auth will be set to None. -#### Setting the authentication scheme +# +## Setting the authentication scheme The default authentication schemes may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting. For example. ```python @@ -102,7 +104,8 @@ class IsAuthenticatedOrReadOnly(BasePermission): request.user.is_authenticated ) ``` -#### How permissions are determined +# +## How permissions are determined Permissions in REST framework are always defined as a list of permission classes. Before running the main body of the view each permission in the list is checked. If any permission check fails, an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised, and the main body of the view will not run. @@ -134,8 +137,9 @@ Then, in the views file: ```python permission_classes = [IsOwnerOrReadOnly] ``` +# -#### Custom Model Permissions +## Custom Model Permissions To create custom permissions for a given model object, use the permissions model Meta attribute. @@ -154,8 +158,10 @@ The only thing this does is create those extra permissions when you run manage.p ```python user.has_perm('app.close_task') ``` +# # Logging -# [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#logging) +[Source](https://docs.djangoproject.com/en/4.1/topics/logging/#logging) + Logging is an important part of every application life cycle. Having a good logging system becomes a key feature that helps developers, sysadmins, and support teams to understand and solve appearing problems. The Python standard library and Django already comes with an integrated logging module that provides basic as well as advanced logging features. Log messages can give helpful information about various events happening behind the scenes. @@ -165,7 +171,7 @@ A Python logging configuration consists of four parts: - Filters - Formatters -##### Loggers +## `Loggers` A logger is the entry point into the logging system. Each logger is a named bucket to which messages can be written for processing. A logger is configured to have a log level. This log level describes the severity of the messages that the logger will handle. Python defines the following log levels: @@ -176,17 +182,17 @@ A logger is configured to have a log level. This log level describes the severit - CRITICAL: Information describing a critical problem that has occurred. -##### Handlers +## `Handlers` The handler is the engine that determines what happens to each message in a logger. It describes a particular logging behavior, such as writing a message to the screen, to a file, or to a network socket. -##### Filters +## `Filters` A filter is used to provide additional control over which log records are passed from logger to handler. By default, any log message that meets log level requirements will be handled. However, by installing a filter, you can place additional criteria on the logging process. For example, you could install a filter that only allows ERROR messages from a particular source to be emitted. -##### Formatters +## `Formatters` Ultimately, a log record needs to be rendered as text. Formatters describe the exact format of that text. A formatter usually consists of a Python formatting string containing LogRecord attributes; however, you can also write custom formatters to implement specific formatting behavior. -##### Default logging definition +## Default logging definition Django’s default logging configuration inherits Python’s defaults. It’s available as django.utils.log.DEFAULT_LOGGING and defined in django/utils/log.py: ```python { @@ -238,7 +244,7 @@ Django’s default logging configuration inherits Python’s defaults. It’s av } ``` -##### Basic Custom Logging Configuration [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#topic-logging-parts-loggers) +## Basic Custom Logging Configuration [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#topic-logging-parts-loggers) # Logging configuration that allows to output warnings in the console: ```python @@ -282,7 +288,7 @@ LOGGING = { ### Different types of Logging -##### Basic add instance log +## `Basic add instance log` # ```python import logging,traceback @@ -303,7 +309,7 @@ def home(request): logger.warning('Home page was accessed at '+str(datetime.datetime.now())+' hours') ``` -## Audit logs +## `Audit logs` Audit logs is the process of keeping track of the activity within some piece of software. It logs the event, the time which it happened and the responsible. There are several django libraries that offers this feature, one of those is the django-auditlog. ### django-auditlog [Source](https://django-auditlog.readthedocs.io/en/latest/usage.html) @@ -328,7 +334,7 @@ auditlog.register(MyModel) # # Serializers -# + ````python from rest_framework import serializers from backend_api.models import Movie @@ -352,7 +358,7 @@ serializer.errors # {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']} ``` -##### Inspecting a ModelSerializer +## Inspecting a ModelSerializer Serializer classes generate helpful verbose representation strings, that allow you to fully inspect the state of their fields. This is particularly useful when working with ModelSerializers where you want to determine what set of fields and validators are being automatically created for you. Allow blank and null: @@ -365,7 +371,7 @@ Regex validation: class UserSerializer(serializers.ModelSerializer): first_name = serializers.RegexField(regex=r'^[a-zA-Z -.\'\_]+$', required=True) ``` -##### Field-level validation +## Field-level validation Check that the blog post is about Django: ```python from rest_framework import serializers @@ -388,7 +394,7 @@ def validate_rating(self, value): return value ``` -##### Object-level validation +## Object-level validation To do any other validation that requires access to multiple fields, add a method called .validate() to your Serializer subclass. This method takes a single argument, which is a dictionary of field values. It should raise a serializers.ValidationError if necessary, or just return the validated values. For example: ```python from rest_framework import serializers @@ -407,7 +413,7 @@ class EventSerializer(serializers.Serializer): return data ``` -##### Custom Validator (Example) +## Custom Validator (Example) Individual fields on a serializer can include validators, by declaring them on the field instance, for example: ```python def multiple_of_ten(value): @@ -432,7 +438,7 @@ class MovieViewSet(viewsets.ModelViewSet): queryset = Movie.objects.all() ```` -### Adding authentication permissions to views (Example) +## Adding authentication permissions to views (Example) REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access. [(Link)](https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/) ````python @@ -445,7 +451,7 @@ class MovieViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticatedOrReadOnly] ```` -### Different permissions for different actions +## Different permissions for different actions You may inspect these attributes to adjust behaviour based on the current action. For example, you could restrict permissions to everything except the list action similar to this: ```python def get_permissions(self): @@ -459,7 +465,7 @@ def get_permissions(self): return [permission() for permission in permission_classes] ``` -### Adding addicional validations to default ModelViewSetFunctions (Example) +## Adding addicional validations to default ModelViewSetFunctions (Example) This function calls the given model and get object from that if that object or model doesn't exist it raise 404 error. ````python def retrieve(self, request, pk=None, *args, **kwargs): @@ -468,7 +474,7 @@ def retrieve(self, request, pk=None, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK) ```` -### Extra actions with validation (Example) +## Extra actions with validation (Example) Getting the Movie director by using an extra action with a different serializer ````python @@ -511,7 +517,7 @@ Another example using actions: return Response(serializer.data) ```` -#### If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) +## If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) ```python def check_positive_numbers(value1, value2): diff --git a/docs/crud/2-frontend.md b/docs/crud/2-frontend.md index 496239b..06f5789 100644 --- a/docs/crud/2-frontend.md +++ b/docs/crud/2-frontend.md @@ -6,7 +6,7 @@ # Authentication [Source](https://nextjs.org/docs/authentication) -## Authenticating Statically Generated Pages +## `Authenticating Statically Generated Pages` Next.js automatically determines that a page is static if there are no blocking data requirements. This means the absence of getServerSideProps and getInitialProps in the page. Instead, your page can render a loading state from the server, followed by fetching the user client-side. One advantage of this pattern is it allows pages to be served from a global CDN and preloaded using next/link. In practice, this results in a faster TTI (Time to Interactive). Let's look at an example for a profile page. This will initially render a loading skeleton. Once the request for a user has finished, it will show the user's name: @@ -37,8 +37,9 @@ const Profile = () => { export default Profile ``` -##### Authenticating Server-Rendered Pages (Example) # +## `Authenticating Server-Rendered Pages (Example)` + ```sh // pages/profile.js @@ -74,9 +75,10 @@ const Profile = ({ user }) => { export default Profile ``` - +# # Validation -#### Built-in Form validation (Example) [Source](https://nextjs.org/docs/guides/building-forms) + +## `Built-in Form validation (Example)` [Source](https://nextjs.org/docs/guides/building-forms) ```sh
@@ -98,7 +100,7 @@ export default Profile With these validation checks in place, when a user tries to submit an empty field for Name, it gives an error that pops right in the form field. Similarly, a roll number can only be entered if it's 10-20 characters long. -## JavaScript-based Form Validation (Example) +## `JavaScript-based Form Validation (Example)` Form Validation is important to ensure that a user has submitted the correct data, in a correct format. JavaScript offers an additional level of validation along with HTML native form attributes on the client side. Developers generally prefer validating form data through JavaScript because its data processing is faster when compared to server-side validation, however front-end validation may be less secure in some scenarios as a malicious user could always send malformed data to your server. ```tsx @@ -118,7 +120,7 @@ Form Validation is important to ensure that a user has submitted the correct dat } ``` # TypeScript -## Creating TypeScript types in Next.js [Source](https://blog.logrocket.com/using-next-js-with-typescript/) +## `Creating TypeScript types in Next.js` [Source](https://blog.logrocket.com/using-next-js-with-typescript/) You can create types for anything in your application, including prop types, API responses, arguments for your utility functions, and even properties of your global state. @@ -179,7 +181,7 @@ export default AddPost ``` # Responses -## Response Types +## `Response Types` Another thing you might be using is the API routes from Next.js. ```tsx export default function handler(req, res) { @@ -197,15 +199,15 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) { # Axios -## Front-end - Back-end connection -#### Using the Axios API [Source](https://www.digitalocean.com/community/tutorials/react-axios-react) +## `Front-end - Back-end connection` +## Using the Axios API [Source](https://www.digitalocean.com/community/tutorials/react-axios-react) Axios is promise-based, which gives you the ability to take advantage of JavaScript’s async and await for more readable asynchronous code. ```sh npm install axios ``` -### Response type conversion +## Response type conversion Sometimes the data coming from the API has values that may break our application. Due to this fact you should always parse the response. For example: ```js From 97223d3c0acb43ae043a6b93e28c8ea168012e9b Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Wed, 31 Aug 2022 15:18:59 +0100 Subject: [PATCH 08/11] . --- docs/crud/0-index.md | 3 +++ docs/crud/01-checklist.md | 13 ++++++++++++- docs/crud/1-backend.md | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/crud/0-index.md b/docs/crud/0-index.md index ee3a8c0..a59b4af 100644 --- a/docs/crud/0-index.md +++ b/docs/crud/0-index.md @@ -16,6 +16,9 @@ Meaning of CRUD: acronym referring to the 4 basic functions that are executed in # +## Development checkllist +[Checklist](01-checklist.md) + ## Backend flow and steps [Flow - Backend](1-backend.md) ## Frontend flow and steps diff --git a/docs/crud/01-checklist.md b/docs/crud/01-checklist.md index 42ea612..0d6208b 100644 --- a/docs/crud/01-checklist.md +++ b/docs/crud/01-checklist.md @@ -30,4 +30,15 @@ - [Responses](2-frontend.md#responses) -- [Axios](2-frontend.md#axios) \ No newline at end of file +- [Axios](2-frontend.md#axios) + + +# +## Development checkllist +[Index](0-index.md) + +## Backend flow and steps +[Flow - Backend](1-backend.md) +## Frontend flow and steps +[Flow - FrontEnd](2-frontend.md) + diff --git a/docs/crud/1-backend.md b/docs/crud/1-backend.md index d514e5f..d795947 100644 --- a/docs/crud/1-backend.md +++ b/docs/crud/1-backend.md @@ -1,4 +1,4 @@ -[Flow - Backend](0-index.md) +[Index](0-index.md) # Flow (Backend) ### Assuming you already have an project created, let's skip the create project and app steps. From be947d5aa0a3f0845fcb3712ad6c3d5b8a0a80de Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Wed, 31 Aug 2022 15:23:41 +0100 Subject: [PATCH 09/11] . --- docs/crud/1-backend.md | 55 +++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/docs/crud/1-backend.md b/docs/crud/1-backend.md index d795947..f7dcf3e 100644 --- a/docs/crud/1-backend.md +++ b/docs/crud/1-backend.md @@ -19,7 +19,7 @@ class Movie(models.Model): ```` -### Apply models to the DB +### `Apply models to the DB` # ```sh python manage.py makemigrations @@ -27,7 +27,7 @@ python manage.py makemigrations ```sh python manage.py migrate ``` -### Create Super User +### `Create Super User` # ```sh python manage.py createsuperuser @@ -43,7 +43,7 @@ The authentication schemes are always defined as a list of classes. REST framewo If no class authenticates, request.user will be set to an instance of django.contrib.auth.models.AnonymousUser, and request.auth will be set to None. # -## Setting the authentication scheme +## `Setting the authentication scheme` The default authentication schemes may be set globally, using the DEFAULT_AUTHENTICATION_CLASSES setting. For example. ```python @@ -105,7 +105,7 @@ class IsAuthenticatedOrReadOnly(BasePermission): ) ``` # -## How permissions are determined +## ` How permissions are determined` Permissions in REST framework are always defined as a list of permission classes. Before running the main body of the view each permission in the list is checked. If any permission check fails, an exceptions.PermissionDenied or exceptions.NotAuthenticated exception will be raised, and the main body of the view will not run. @@ -116,7 +116,7 @@ When the permission checks fail, either a "403 Forbidden" or a "401 Unauthorized - The request was not successfully authenticated, and the highest priority authentication class does not use WWW-Authenticate headers. — An HTTP 403 Forbidden response will be returned. - The request was not successfully authenticated, and the highest priority authentication class does use WWW-Authenticate headers. — An HTTP 401 Unauthorized response, with an appropriate WWW-Authenticate header will be returned. -#### Custom Permissions (Example) +#### `Custom Permissions (Example)` ```python class IsOwnerOrReadOnly(permissions.BasePermission): """ @@ -139,7 +139,7 @@ permission_classes = [IsOwnerOrReadOnly] ``` # -## Custom Model Permissions +## `Custom Model Permissions ` To create custom permissions for a given model object, use the permissions model Meta attribute. @@ -192,7 +192,7 @@ By default, any log message that meets log level requirements will be handled. H ## `Formatters` Ultimately, a log record needs to be rendered as text. Formatters describe the exact format of that text. A formatter usually consists of a Python formatting string containing LogRecord attributes; however, you can also write custom formatters to implement specific formatting behavior. -## Default logging definition +## `Default logging definition` Django’s default logging configuration inherits Python’s defaults. It’s available as django.utils.log.DEFAULT_LOGGING and defined in django/utils/log.py: ```python { @@ -244,7 +244,7 @@ Django’s default logging configuration inherits Python’s defaults. It’s av } ``` -## Basic Custom Logging Configuration [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#topic-logging-parts-loggers) +## `Basic Custom Logging Configuration` [Source](https://docs.djangoproject.com/en/4.1/topics/logging/#topic-logging-parts-loggers) # Logging configuration that allows to output warnings in the console: ```python @@ -287,9 +287,9 @@ LOGGING = { ``` -### Different types of Logging -## `Basic add instance log` -# +## `Different types of Logging` +### `Basic add instance log` + ```python import logging,traceback logger = logging.getLogger('django') @@ -298,8 +298,8 @@ def addSomeInstance(request): //View logic logger.warning('Added instance!') ``` -##### Access logs -# +### `Access logs` + ```python import datetime import logging @@ -312,7 +312,7 @@ def home(request): ## `Audit logs` Audit logs is the process of keeping track of the activity within some piece of software. It logs the event, the time which it happened and the responsible. There are several django libraries that offers this feature, one of those is the django-auditlog. -### django-auditlog [Source](https://django-auditlog.readthedocs.io/en/latest/usage.html) +### `django-auditlog` [Source](https://django-auditlog.readthedocs.io/en/latest/usage.html) Auditlog can automatically log changes to objects for you. This functionality is based on Django’s signals, but linking your models to Auditlog is even easier than using signals. Registering your model for logging can be done with a single line of code, as the following example illustrates: @@ -332,7 +332,7 @@ auditlog.register(MyModel) ``` -# + # Serializers ````python @@ -345,7 +345,7 @@ class MovieSerializer(serializers.ModelSerializer): fields = '__all__' ```` # -#### Serializer Validations - [Source](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) +#### `Serializer Validations` - [Source](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) By default, all the model fields on the class will be mapped to a corresponding serializer fields. Any relationships such as foreign keys on the model will be mapped to PrimaryKeyRelatedField. Reverse relationships are not included by default unless explicitly included as specified in the serializer relations documentation. @@ -358,7 +358,7 @@ serializer.errors # {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']} ``` -## Inspecting a ModelSerializer +## `Inspecting a ModelSerializer` Serializer classes generate helpful verbose representation strings, that allow you to fully inspect the state of their fields. This is particularly useful when working with ModelSerializers where you want to determine what set of fields and validators are being automatically created for you. Allow blank and null: @@ -371,7 +371,7 @@ Regex validation: class UserSerializer(serializers.ModelSerializer): first_name = serializers.RegexField(regex=r'^[a-zA-Z -.\'\_]+$', required=True) ``` -## Field-level validation +## `Field-level validation` Check that the blog post is about Django: ```python from rest_framework import serializers @@ -394,7 +394,7 @@ def validate_rating(self, value): return value ``` -## Object-level validation +## `Object-level validation` To do any other validation that requires access to multiple fields, add a method called .validate() to your Serializer subclass. This method takes a single argument, which is a dictionary of field values. It should raise a serializers.ValidationError if necessary, or just return the validated values. For example: ```python from rest_framework import serializers @@ -413,7 +413,7 @@ class EventSerializer(serializers.Serializer): return data ``` -## Custom Validator (Example) +## `Custom Validator (Example)` Individual fields on a serializer can include validators, by declaring them on the field instance, for example: ```python def multiple_of_ten(value): @@ -424,11 +424,6 @@ class GameRecord(serializers.Serializer): score = IntegerField(validators=[multiple_of_ten]) ``` - - - - - # Views As seen in Django Rest Framework docs [(Link)](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset), the ModelViewSet class inherits from GenericAPIView and includes implementations for various actions, by mixing in the behavior of the various mixin classes. The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). @@ -438,7 +433,7 @@ class MovieViewSet(viewsets.ModelViewSet): queryset = Movie.objects.all() ```` -## Adding authentication permissions to views (Example) +## `Adding authentication permissions to views (Example)` REST framework includes a number of permission classes that we can use to restrict who can access a given view. In this case the one we're looking for is IsAuthenticatedOrReadOnly, which will ensure that authenticated requests get read-write access, and unauthenticated requests get read-only access. [(Link)](https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/) ````python @@ -451,7 +446,7 @@ class MovieViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticatedOrReadOnly] ```` -## Different permissions for different actions +## `Different permissions for different actions` You may inspect these attributes to adjust behaviour based on the current action. For example, you could restrict permissions to everything except the list action similar to this: ```python def get_permissions(self): @@ -465,7 +460,7 @@ def get_permissions(self): return [permission() for permission in permission_classes] ``` -## Adding addicional validations to default ModelViewSetFunctions (Example) +## `Adding addicional validations to default ModelViewSetFunctions (Example)` This function calls the given model and get object from that if that object or model doesn't exist it raise 404 error. ````python def retrieve(self, request, pk=None, *args, **kwargs): @@ -474,7 +469,7 @@ def retrieve(self, request, pk=None, *args, **kwargs): return Response(serializer.data, status=status.HTTP_200_OK) ```` -## Extra actions with validation (Example) +## `Extra actions with validation (Example)` Getting the Movie director by using an extra action with a different serializer ````python @@ -517,7 +512,7 @@ Another example using actions: return Response(serializer.data) ```` -## If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) +#### If a custom endpoint doesn't use Serializer, we must validate each param individually (Example) ```python def check_positive_numbers(value1, value2): From 50037f16efc9df2381a02cefc3c3307ad512df23 Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Wed, 31 Aug 2022 16:28:57 +0100 Subject: [PATCH 10/11] . --- docs/crud/1-backend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/crud/1-backend.md b/docs/crud/1-backend.md index f7dcf3e..3a3640d 100644 --- a/docs/crud/1-backend.md +++ b/docs/crud/1-backend.md @@ -37,7 +37,7 @@ python manage.py createsuperuser Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted. -#### How authentication is determined +## How authentication is determined The authentication schemes are always defined as a list of classes. REST framework will attempt to authenticate with each class in the list, and will set request.user and request.auth using the return value of the first class that successfully authenticates. If no class authenticates, request.user will be set to an instance of django.contrib.auth.models.AnonymousUser, and request.auth will be set to None. From 8d5dc82fd6327ae934e756b3d6d119a1bf92dddb Mon Sep 17 00:00:00 2001 From: Miguel Rodrigues Date: Wed, 31 Aug 2022 16:40:42 +0100 Subject: [PATCH 11/11] . --- docs/crud/01-checklist.md | 2 +- docs/crud/1-backend.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/crud/01-checklist.md b/docs/crud/01-checklist.md index 0d6208b..91c3e46 100644 --- a/docs/crud/01-checklist.md +++ b/docs/crud/01-checklist.md @@ -34,7 +34,7 @@ # -## Development checkllist +## Index [Index](0-index.md) ## Backend flow and steps diff --git a/docs/crud/1-backend.md b/docs/crud/1-backend.md index 3a3640d..0f4257b 100644 --- a/docs/crud/1-backend.md +++ b/docs/crud/1-backend.md @@ -37,7 +37,7 @@ python manage.py createsuperuser Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The permission and throttling policies can then use those credentials to determine if the request should be permitted. -## How authentication is determined +## `How authentication is determined` The authentication schemes are always defined as a list of classes. REST framework will attempt to authenticate with each class in the list, and will set request.user and request.auth using the return value of the first class that successfully authenticates. If no class authenticates, request.user will be set to an instance of django.contrib.auth.models.AnonymousUser, and request.auth will be set to None.