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 src
s 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
Hashes for restframework_datachange-0.0.2.1.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | cc9df70da9bfbb395e6f47b83f1e3fb9bbf3834b40bdc4f2845489b762bafcca |
|
MD5 | f45b7e1d460d14954a223e8c5da2bb67 |
|
BLAKE2b-256 | 8c3e56435418e3b3aa3bff1debdccda07fb9a107fe1159552e010e5ae583971a |
Hashes for restframework_datachange-0.0.2.1-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f63d8275696a94c43195116b7477ceedb1060b8fe4442fa7b155e779d86e142f |
|
MD5 | 8ae49a6aa9468198ce70fc2ed36bc159 |
|
BLAKE2b-256 | 1afd01376b3d7c8d8b1a85ea37882fc8d91ce6a0339e51c31ce8898384f92c4a |