Change data in ModelViewSet
Project description
Restframework-datachange is an enhancement of rest_framework. To use it, we must have basic understandings of Django and rest_framework.
1. Installation
As usual, use pip:
$ pip install restframework_datachange
$ pip install django
$ pip install djangorestframework
2. Before using restframework_datachange
We make a sample restframework-support project.
$ django-admin startproject my_project
my_project$ cd my_project
my_project$ django-admin startapp movie
Now we write the files as followed:
Make models.
my_project/movie/models.py
from django.db import models
class Cast(models.Model):
SEX_CHOICE = ((1, "Male"), (2, "Female"))
sex = models.IntegerField(choices=SEX_CHOICE, default=1)
profession = models.CharField(max_length=100, default="")
foreign_name = models.CharField(max_length=100, default="")
def __str__(self):
return self.foreign_name
class OneMovie(models.Model):
COUNTRY_CHOICE = ((1, "UK"), (2, "US"))
name = models.CharField(max_length=200, default="")
director = models.ManyToManyField(Cast, related_name="director")
actors = models.ManyToManyField(Cast, related_name="actors")
country = models.IntegerField(default="", choices=COUNTRY_CHOICE)
def __str__(self):
return self.name
Make Serializers. For usage of SlugRelatedField, please refer to the document of djangorestframework.
my_project/movie/serializers.py
from rest_framework.serializers import ModelSerializer, SlugRelatedField
from movie.models import OneMovie, Cast
class MovieSerializer(ModelSerializer):
director = SlugRelatedField(slug_field="foreign_name", queryset=Cast.objects.all(), many=True)
actors = SlugRelatedField(slug_field="foreign_name", queryset=Cast.objects.all(), many=True)
class Meta:
model = OneMovie
fields = "__all__"
Make views
my_project/movie/views.py
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from movie.models import OneMovie
from movie.serializers import MovieSerializer
class MovieViewSet(ModelViewSet):
queryset = OneMovie.objects.all()
serializer_class = MovieSerializer
Change settings
my_project/my_project/settings.py
...
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework", #
"movie" #
]
...
Make migrations and migrate.
my_project/my_project $ cd ..
my_project $ python manage.py makemigrations
Migrations for 'movie':
movie/migrations/0001_initial.py
- Create model Cast
- Create model OneMovie
my_project$ python3 manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, movie, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying movie.0001_initial... OK
Applying sessions.0001_initial... OK
Make URLs for the whole project.
my_project/my_project/urls.py
from django.contrib import admin
from django.urls import path
from rest_framework import routers
from movie.views import MovieViewSet
urlpatterns = [
path("admin/", admin.site.urls),
]
router = routers.DefaultRouter()
router.register(r"movie", MovieViewSet, base_name="movie")
urlpatterns += router.urls
Now we create some data for the database.
my_project $ python manage.py shell
In [1]: from movie.models import OneMovie
In [2]: from movie.models import Cast
In [3]: daniel = Cast.objects.create(sex=1, profession="actor", foreign_name="Daniel")
In [4]: daniel
Out[4]: <Cast: Daniel>
In [5]: emma = Cast.objects.create(sex=2, profession="actor", foreign_name="Emma")
In [6]: Cast.objects.all()
Out[6]: <QuerySet [<Cast: Daniel>, <Cast: Emma>]>
In [7]: david = Cast.objects.create(sex=1, profession="director", foreign_name="David")
In [8]: harry_potter_movie = OneMovie.objects.create(name="Harry Potter and the Goblet of Fire", country=2)
In [9]: harry_potter_movie.director.add(david)
In [10]: harry_potter_movie.actors.add(daniel)
In [11]: harry_potter_movie.actors.add(emma)
In [12]: harry_potter_movie.save()
In [13]: harry_potter_movie
Out[13]: <OneMovie: Harry Potter and the Goblet of Fire>
In [14]: harry_potter_movie.director.all()
Out[14]: <QuerySet [<Cast: David>]>
In [15]: harry_potter_movie.actors.all()
Out[16]: <QuerySet [<Cast: Daniel>, <Cast: Emma>]>
In [16]: exit()
Now we run the server.
my_project $ python manage.py runserver 0.0.0.0:8000
Start your browser and enter http://127.0.0.1:8000/, click "movie": "http://127.0.0.1:8000/movie/"
Ta da! Everything seems perfect. Below is what you'll see.
[
{
"id": 1,
"director": [
"David"
],
"actors": [
"Daniel",
"Emma"
],
"name": "Harry Potter and the Goblet of Fire",
"country": 2
}
]
3. Changing Data
We just want the movie "name" to be "Harry" instead of "Harry Potter and the Goblet of File". That is to say, we want to change the data returned. That's when restframework_datachange comes in!
my_project/movie/views.py
from restframework_datachange.viewsets import RModelViewSet #
from movie.models import OneMovie
from movie.serializers import MovieSerializer
class MovieAdjust(object): #
def change_name(self, value): # xx = name
return value.split(" ")[0] #
class MovieViewSet(MovieAdjust, RModelViewSet): #
queryset = OneMovie.objects.all()
serializer_class = MovieSerializer
By adding a change_xx to the Adjust object and changing inheritance to RModelViewSet, we change the value of the returned "name" field from "Harry Potter and the Goblet of File" to "Harry"!
[
{
"id": 1,
"director": [
"David"
],
"actors": [
"Daniel",
"Emma"
],
"name": "Harry",
"country": 2
}
]
Now we change the "country" code into correspondent country name:
my_project/movie/views.py
...
class MovieAdjust(object):
def change_name(self, value):
return value.split(" ")[0]
def change_country(self, value):
dic = {1: "UK", 2: "US"}
return dic[value]
...
Ta da!
[
{
"id": 1,
"director": [
"David"
],
"actors": [
"Daniel",
"Emma"
],
"name": "Harry",
"country": "US"
}
]
4. Adding new data based on one field
We want a new field that is based on the data's original field. For example, we want a string version of "actors" named as "string_actors".
my_project/movie/views.py
...
class MovieAdjust(object):
string_actors_src1 = "actors"
def change_name(self, value):
return value.split(" ")[0]
def change_country(self, value):
dic = {1: "UK", 2: "US"}
return dic[value]
def add_string_actors(self, value):
return ", ".join(value)
...
Make an add_xx method, pass a value and modify it, and specify the source field as xx_src1. Then you can see this.
[
{
"id": 1,
"director": [
"David"
],
"actors": [
"Daniel",
"Emma"
],
"name": "Harry",
"country": "US",
"string_actors": "Daniel, Emma"
}
]
4. Adding new data based on two or more fields
Suppose we want to join the "country" field and the "name" field to form a "detail" field.
class MovieAdjust(object):
string_actors_src1 = "actors"
detail_src1 = "country" # xx = detail
detail_src2 = "name" # xx = detail
def change_name(self, value):
return value.split(" ")[0]
def change_country(self, value):
dic = {1: "UK", 2: "US"}
return dic[value]
def add_string_actors(self, value):
return ", ".join(value)
def add_detail(self, *value): # xx = detail
return ", ".join([str(i) for i in value])
Now you can see:
[
{
"id": 1,
"director": [
"David"
],
"actors": [
"Daniel",
"Emma"
],
"name": "Harry",
"country": "US",
"detail": "2, Harry Potter and the Goblet of Fire",
"string_actors": "Daniel, Emma"
}
]
Pay attention to the "country" part in "detail", it is the original value 2 instead of the modified value "US". Here, "detail" has source fields "country" and "name", so value[0] is the value of "country" field, 2. And value[1] is the value of "name", "Harry Potter and the Goblet of Fire".
You can also write the code like this:
class MovieAdjust(object):
string_actors_src1 = "actors"
detail_src1 = "country"
detail_src2 = "name"
def change_name(self, value):
return value.split(" ")[0]
def change_country(self, value):
dic = {1: "UK", 2: "US"}
return dic[value]
def add_string_actors(self, value):
return ", ".join(value)
def add_detail(self, country, name):
return str(country) + ", " + str(name)
Make sure the number and the position of parameters beside self are the same as the srcs of your newly-created field.
5. Meddle with other actions.
If you happen to know the retrieve, create, update, actions of restframework, you can meddle your return by creating Adjust objects based on the tables below:
| Actions | Modification | Method Prefix | Source Field Suffix |
|---|---|---|---|
| list | add a field | add | src |
| list | change a field | change | -- |
| retrieve | add a field | append | org |
| retrieve | change a field | modify | -- |
| create | add a field | attach | bir |
| create | change a field | reform | -- |
| update | add a field | adjoin | lch |
| update | change a field | vary | -- |
For example:
http://127.0.0.1:8000/movie/1/ calls the retrieve method.
class MovieAdjust(object):
string_actors_src1 = "actors"
detail_src1 = "country" # xx = detail(list)
detail_src2 = "name" # xx = detail(list)
string_actors_retrieve_org1 = "actors" # yy = string_actors_retrieve(retrieve)
def change_name(self, value):
return value.split(" ")[0]
def change_country(self, value):
dic = {1: "UK", 2: "US"}
return dic[value]
def add_string_actors(self, value):
return ", ".join(value)
def add_detail(self, *value): # xx = detail
return ", ".join([str(i) for i in value])
def modify_country(self, value):
dic = {1: "United Kingdom", 2: "United States"}
return dic[value]
def append_string_actors_retrieve(self, value):
# yy = string_actors_retrieve
return ", ".join(value)
{
"id": 1,
"director": [
"David"
],
"actors": [
"Daniel",
"Emma"
],
"name": "Harry Potter and the Goblet of Fire",
"country": "United States",
"string_actors_retrieve": "Daniel, Emma"
}
6. Show/hide a field
If you want to show or hide a field, you can modify the action-specific _fields and _exclude property.
class MovieAdjust(object):
list_exclude = ["actors"]
will get you:
[
{
"id": 1,
"director": [
"David"
],
"name": "Harry Potter and the Goblet of Fire",
"country": 2
}
]
class MovieAdjust(object):
list_fields = ["actors", "name"]
will get you:
[
{
"actors": [
"Daniel",
"Emma"
],
"name": "Harry Potter and the Goblet of Fire"
}
]
7. Turn python dict into a model
If you have a python dict and wants to turn it into a Django model, you can use modelmaker.
model_maker(dic, file='fake_model.py', class_name='Default', name_changer=camel_to_, default_settings=None, **config)
dic : A python dic.
file : The model will be written in the file specified. Passed as "" and it will only return a printed version.
class_name : Model name.
name_changer : A method to change some string into another string. Default is a function that turns camel cases to underlines.
default_settings: A python dict to specify default properties settings of a type of field.
config : Set the properties of model. For detail, please refer to the documents of django.
Example:
from restframework_datachange.model_maker import model_maker
from datetime import datetime
dic = {
"apple": 1, # IntegerField
"boy": 1.2, # FloatField
"cat": "string", # CharField
"dog": [{"json": 1}], # JSONField
"elephant": datetime.now(), # DatetimeField
"changed": "string"
}
config = {
"apple__choices": [(1, "UK"), (2, "US")],
"boy__null": True,
"cat__max_length": 20,
"dog__verbose_name": "Dog",
"elephant__auto_now": True
}
def name_changer(string):
li = ["apple", "boy", "cat", "dog", "elephant"]
if string not in li:
return "fog"
return string
print(model_maker(dic, file="", class_name="M", name_changer=name_changer, **config))
will give you:
from django.contrib.postgres.fields import JSONField
from django.db import models
class M(models.Model):
apple = models.IntegerField(verbose_name="", help_text="", null=True, choices=[(1, "UK"), (2, "US")])
boy = models.FloatField(verbose_name="", help_text="", null=True)
cat = models.CharField(verbose_name="", help_text="", default="", max_length=20)
dog = JSONField(verbose_name="Dog", help_text="", null=True, blank=True)
elephant = models.DateTimeField(verbose_name="", help_text="", auto_now=True, auto_now_add=False)
fog = models.CharField(verbose_name="", help_text="", default="", max_length=64)
You can change the default settings of model_maker by passing a python dict to the default_settings parameter.
dic = {"apple": 1} # IntegerField
print(model_maker(dic, file="", class_name="AppleCart", default_settings={"int": {"null":True}}))
will give you:
from django.db import models
class AppleCart(models.Model):
apple = models.IntegerField(null=True)
Default settings for different types are as followed:
{
"int": {"verbose_name": "", "help_text": "", "null": True},
"str": {"verbose_name": "", "help_text": "", "default": "", "max_length": 64},
"datetime": {"verbose_name": "", "help_text": "", "auto_now": False, "auto_now_add": False},
"date": {"verbose_name": "", "help_text": "", "auto_now": False, "auto_now_add": False},
"json": {"verbose_name": "", "help_text": "", "null": True, "blank": True},
"bool": {"verbose_name": "", "help_text": "", "null": True},
"float": {"verbose_name": "", "help_text": "", "null": True}
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file restframework_datachange-0.0.2.1.tar.gz.
File metadata
- Download URL: restframework_datachange-0.0.2.1.tar.gz
- Upload date:
- Size: 15.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.32.2 CPython/3.6.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc9df70da9bfbb395e6f47b83f1e3fb9bbf3834b40bdc4f2845489b762bafcca
|
|
| MD5 |
f45b7e1d460d14954a223e8c5da2bb67
|
|
| BLAKE2b-256 |
8c3e56435418e3b3aa3bff1debdccda07fb9a107fe1159552e010e5ae583971a
|
File details
Details for the file restframework_datachange-0.0.2.1-py2.py3-none-any.whl.
File metadata
- Download URL: restframework_datachange-0.0.2.1-py2.py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.13.0 pkginfo/1.5.0.1 requests/2.22.0 setuptools/41.0.1 requests-toolbelt/0.9.1 tqdm/4.32.2 CPython/3.6.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f63d8275696a94c43195116b7477ceedb1060b8fe4442fa7b155e779d86e142f
|
|
| MD5 |
8ae49a6aa9468198ce70fc2ed36bc159
|
|
| BLAKE2b-256 |
1afd01376b3d7c8d8b1a85ea37882fc8d91ce6a0339e51c31ce8898384f92c4a
|