> For the complete documentation index, see [llms.txt](https://bamtech.gitbook.io/dev-standards/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bamtech.gitbook.io/dev-standards/backend/django/create-model-and-api.mo.md).

# create-model-and-api.mo

## Owner: [Alice Breton](https://github.com/AliceB08)

## Prerequisites (\~45 mins)

* Have a django project installed with an app created

  [see this](https://github.com/bamlab/dev-standards/blob/master/backend/django/getting-started.mo.md).
* In this example we will use an already existing model `city` in a `locations` app, to show how to integrate other

  models with foreign keys.

## Steps

* In this tutorial we will create a `News` model, add it to the Django admin back-office and create a GET API route to

  retrieve all the news that have been created.

### Create a News model *(\~10 min)*

* In this example we will create a new model called `News` that has different attributes as you will see bellow. In the

  *models.py* file of your app (in our case `Publications`) create a new model.

```python
# File: "our_django_project/publications/models.py"
from django.db import models

class News(models.Model):
    title = models.CharField(max_length=50)
    description = models.TextField(max_length=1000)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    city = models.ForeignKey('locations.city')

    class Meta:
        verbose_name_plural = "News"

    def __str__(self):
        return self.title
```

* `CharField` will return a single line form in the admin; max\_length speaks for itself.

  [Here is the Django doc for more information](https://docs.djangoproject.com/en/1.11/ref/models/fields/).
* `TextField` will return a scrollable paragraph in the admin.
* `auto_now_add` generates automatically a timestamp when a news is created.
* `auto_now` updates the timestamp every time you change the news.
* Concerning the city attribute which is a **many to one** relationship: every piece of news is linked to one city, two pieces of news can have the same city.
* It's standard that the name of a model (not class) is singular, and django automatically puts it to plural for external use. However here, News is a singular/plural english name, so we need to make sure Django does not add a second 's' (Newss) by overriding `verbose_name_plural`
* Finally, make your migrations and migrate. To do this run the following commands in your shell:

```
python3 manage.py makemigrations
python3 manage.py migrate
```

or adapt the previous commands, if you run your project with docker.

> **Note**: Here is an article that goes into more detail [How to Create Django Data Migrations](https://simpleisbetterthancomplex.com/tutorial/2017/09/26/how-to-create-django-data-migrations.html)

#### What you can check!

* [ ] You can now check that the migrations are run and that the model exists in the database:

```bash
python3 manage.py showmigrations
```

* [ ] You should see the last migration in the list.
* [ ] You could also go in the database and check that the table has been created:

```
python3 manage.py dbshell
```

* In the shell display the table (in case of psql `\d`) and check the existence of your model. More info [here](https://docs.djangoproject.com/en/2.0/ref/django-admin/#dbshell)

### Add permissions to a group *(\~10 min)*

* Users that are in groups need permission to access the Model. If you don't have a group yet, you can create one by

  clicking on **group** on the admin web site. We can now add permissions to them that will be automatically added when

  you deploy.

```python
# File: "our_django_project/users/apps.py"
from django.apps import AppConfig
from django.contrib.admin import site
from django.db.models.signals import post_migrate


# This function takes a group and a model and adds all the permissions (read, update, delete) of this model to the group
def add_model_permissions(group, model, ContentType, Permission):
    content_type = ContentType.objects.get_for_model(model)
    permissions = Permission.objects.filter(content_type=content_type)
    for permission in permissions:
        group.permissions.add(permission)
```

* You can than create a migration to add the groups permissions when the migrations are run
* Create an empty migration:

```bash
python manage.py makemigrations --empty users
```

* Then fill in your migration like so:

```python
from django.db import migrations

# This function takes the model News and adds the permissions to the group Mayor Admin
def add_group_permissions(sender, using, apps, **kwargs):
    Group = apps.get_model("auth", "Group")
    Permission = apps.get_model("auth", "Permission")
    ContentType = apps.get_model("contenttypes", "ContentType")
    News = apps.get_model("publications", "News")   #Add this line

    #If the group Mayor Admin doesn't exist, this piece of code creates it.
    if Group.objects.using(using).filter(name='Mayor Admin').exists():
        group = Group.objects.get(name='Mayor Admin')
    else:
        group = Group.objects.using(using).create(name='Mayor Admin')
    add_model_permissions(group, News, ContentType, Permission) #Add this second line

class Migration(migrations.Migration):

    dependencies = [
        ('users', '0001_initial'),
    ]

    operations = [
        migrations.RunPython(add_group_permissions),
    ]
```

* Then run

```bash
python manage.py migrate
```

#### What you can check!

* [ ] Create a new user that will be part of the mayor admin group.
* [ ] Connect yourself as the mayor and check that you can create pieces of news.

### Add the News model to the admin *(\~5 min)*

* We now need to add the model to the admin back-office.

```python
# File: "our_django_project/publications/admin.py"
from django.contrib import admin
from .models import News


@admin.register(News)
class MyNewsAdmin(admin.ModelAdmin):
    list_display = (
        'title',
        'city'
    )
```

If you don't add the `list_display` property, the default column title in the admin will be the result of the magic method **str** returned in the model (you can read more [here](https://www.python-course.eu/python3_magic_methods.php)). `list_display` allows you to add several columns to the admin interface.

Without `list_display`: ![](https://user-images.githubusercontent.com/30256638/32740101-2104d6dc-c8a2-11e7-9838-9c2b15bb60a5.png)

With `list_display`: ![](https://user-images.githubusercontent.com/30256638/32740159-50ac5a40-c8a2-11e7-8b9e-89db21193896.png)

#### What you can check!

* [ ] You can now check that the titles on the admin interface have changed.

> **Note**: you can learn how to add other configuration to your admin by reading the [official documentation](https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#modeladmin-options)

### Serialize the News you get from the database *(\~5 min)*

* Serializers translates Django models into other formats(json, xml). In our project want to get a JSON response. Check out the [official Django doc](https://docs.djangoproject.com/en/1.11/topics/serialization/) for more info.

```python
# File: "our_django_project/publications/serializers.py"
from .models import News
from rest_framework import serializers


class NewsSerializer(serializers.HyperlinkedModelSerializer):
    id = serializers.IntegerField(source='pk')
    city = serializers.StringRelatedField(many=False, source="city.pk")   #We need this to get the primary key of the city that is attached to this news

    class Meta:
        model = News
        fields = ('title', 'description', 'created_at', 'updated_at', 'city', 'id')    #All the fields you wish to get
```

### Create a News ViewSet *(\~5 min)*

* ViewSets will allow you to concentrate on modeling the state and interactions of the API, and leave the URL construction to be handled automatically

> **Note**: Check out the Django doc for more info [ViewSets](http://www.django-rest-framework.org/api-guide/viewsets/)

```python
# File: "our_django_project/publications/viewsets.py"
from .models import News
from rest_framework import viewsets
from .serializers import NewsSerializer

#This is an example of filtering the pieces of news with the city-id like this: /?city-id=5
class FilterByCity(object):
    def get_queryset(self):   #You need to override the default get_queryset method
        queryset = super().get_queryset()
        city_id = self.request.query_params.get('city-id')

        # This filters the queryset by city (if there is a city)
        if city_id is not None:
            queryset = queryset.filter(city=city_id)

        return queryset


class NewsViewSet(FilterByCity, viewsets.ModelViewSet):
    queryset = News.objects.all()
    serializer_class = NewsSerializer
```

* The NewsViewSet class will now inherit both classes FilterByCity viewsets.ModelViewSet from right to left. Therefore, the FilterByCity get\_queryset method will override the ModelViewSet one.

> **Note**: You can also use automatically generated filters ([have a look here](http://www.django-rest-framework.org/api-guide/filtering/#generic-filtering))

### Mount the `News` ViewSet to an endpoint using a router *(\~5 min)*

* Django routers will allow you to easily generate different routes from your ViewSet (GET, POST, ...)

```python
# File: "our_django_project/config/router.py"
from rest_framework import routers
from our_django_project.users.viewsets import UserViewSet
from our_django_project.locations.viewsets import CityViewSet
from our_django_project.publications.viewsets import NewsViewSet  #Add this line


# Routers provide an easy way of automatically determining the URL conf.
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'locations/cities', CityViewSet)
router.register(r'publications/news', NewsViewSet)    #Add this one too
```

> **Note**:Here is the [Django doc](http://www.django-rest-framework.org/api-guide/routers) if you want to learn more about Django routers

#### What you can check!

* [ ] You are good to go! :D
* [ ] If you go on this url: `HOST/publications/news/` you should get something like this:

```javascript
[
  {
    "title": "Reading the article",
    "description": "Hello, I am glad I read this article!",
    "created_at": "2017-11-08T15:31:30.597524Z",
    "updated_at": "2017-11-08T15:31:30.597555Z",
    "city": "66",
    "id": 2
  },
  {
    "title": "Tutorial",
    "description": "I followed the tutorial to create a new model",
    "created_at": "2017-11-08T15:33:28.834694Z",
    "updated_at": "2017-11-08T15:33:28.834721Z",
    "city": "66",
    "id": 3
  },
  {
    "title": "Les Français adorent !",
    "description": "J'ai lu cet article, il m'a aidé à faire mon premier modèle, youhouu !",
    "created_at": "2017-11-08T15:20:33.457740Z",
    "updated_at": "2017-11-08T16:54:19.322703Z",
    "city": "5",
    "id": 1
  }
]
```

* [ ] If you go on this url: `HOST/publications/news/?city-id=5` (that is filtered) you should get something like this:

```javascript
[
  {
    "title": "Les Français adorent !",
    "description": "Je suis française et j'ai lu cet article, il m'a aidé à faire mon premier modèle, youhouu !",
    "created_at": "2017-11-08T15:20:33.457740Z",
    "updated_at": "2017-11-08T16:54:19.322703Z",
    "city": "5",
    "id": 1
  }
]
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bamtech.gitbook.io/dev-standards/backend/django/create-model-and-api.mo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
