Using Django Rest Framework, how to execute update/PUT calls to a non-standard database?
In my Django project I'm using a separate database for the application data:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
'bookstore': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'bookstore.sqlite3'),
}
}
I use the ModelSerializer and ModelViewSet to automatically create the API for me. I loop over the models to automatically generate all the Serializers and ViewSets (quite a big number of tables), the generated classes end up looking like this:
ModelSerializer:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
ModelViewSet:
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.using('bookstore').all()
serializer_class = BookSerializer
I needed to add the using('bookstore') here to make this work.
Reading (GET) works fine. When I try to do a PUT update, I get a no such table: book Sqlite error.
It seems like the update isn't routed to the bookstore database.
For completeness, I use these loops to generate the ModelSerializer and ModelViewSets from the models:
ModelSerializer, generator:
models = dict(apps.all_models['api'])
for name, model in models.items():
class_name = name[:1].upper() + name[1:] + 'Serializer'
Meta = type('Meta', (object, ), {'model': model, 'fields': '__all__'})
print(class_name)
globals()[class_name] = type(class_name, (serializers.ModelSerializer,), {'Meta':Meta, })
ModelViewSet, generator:
models = dict(apps.all_models['api'])
for name, model in models.items():
capitalized_class_name = name[:1].upper() + name[1:]
viewset_class_name = capitalized_class_name + 'ViewSet'
serializer_class_name = capitalized_class_name + 'Serializer'
globals()[viewset_class_name] = type(viewset_class_name, (viewsets.ModelViewSet,), {'queryset': model.objects.using('bookstore').all(), 'serializer_class': globals()[serializer_class_name],'filter_backends': [filters.OrderingFilter], })
Related
I am trying to use multiple different DB in a single app(todo). I am using Djongo package for dealing mongodb.
Settings.py
DATABASES = {
'default':{},
'sql_db': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'my_db',
'USER': '******',
'PASSWORD': '***',
'HOST': 'localhost',
'PORT': '5432',
},
'mongodb':{
'ENGINE': 'djongo',
'NAME': 'mongo_db'
}
}
todo/models.py
class Task(models.Model):
todo = models.CharField(max_length=200)
status = models.BooleanField(default=False)
def __str__(self):
return self.todo
todo/serializers.py
class TodoSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = '__all__'
todo/views.py
#api_view(['POST'])
def todoCreate(request):
serializer = TodoSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
serializer.save(using='mongodb')
return Response(serializer.data)
it successfully saved the record into 'sql_db' but not save in the 'mongodb'.
My suggestion is to put the dual database saving into a save function in models.py:
class Task(models.Model):
...
def save(self, *args, **kwargs):
super(Task, self).save(using='sql_db')
super(Task, self).save(using='mongodb')
and drop the 2nd save in the view.
This is so any save of a Task object will trigger the dual save (E.g. from the django admin or another script) rather than just when it comes from this one serializer view.
I'm not sure the serializer.save method passes the 'uses' through to the objects save function which may be the problem for you. If you want to do it in the serializer then you may have to override the create and save in the TodoSerializer: see Serializers
For dealing with module named Djongo for connecting Django with MongoDb , one should be completely aware of the modules versions being used in the environment.
asgiref==3.5.0,
Django==4.0.3,
djongo==1.3.6,
dnspython==2.2.1,
pykerberos==1.2.4,
pymongo==3.12.1,
python-snappy==0.6.1,
pytz==2022.1,
sqlparse==0.2.4,
configure the Database settings in the settings.py. Its not necessary to use host while connnecting to the localhost of MongoDb.
DATABASES = {
'default': {
'ENGINE': 'djongo',
'NAME': 'your-db-name',
}
}
I am using two databases in my django project:
settings.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
},
"2ndDB": {
"ENGINE": "sql_server.pyodbc",
"NAME": "2nddb-sqldb",
"USER": CRED["user"],
"PASSWORD": CRED["password"],
"HOST": "2ndserver-sqlsrv.database.windows.net",
"PORT": 1433,
"OPTIONS": {"driver": "ODBC Driver 17 for SQL Server",},
},
In my view I try to validate my form:
views.py
def registration(request):
if request.method == "POST":
form = RegistrationForm(request.POST, user=request.user.get_username())
if form.is_valid():
forms.py
class RegistrationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.add_input(Submit("submit", "OK"))
class Meta:
model = Registration
fields = (
"uuid",
"xyz",
)
Which fails as it tries to validate the unique constraint of the pk of the model in the unmanaged db:
models.py
class Registration(models.Model):
uuid = models.UUIDField(primary_key=True, verbose_name="UUID")
class Meta:
db_table = "xyz"
managed = False
It is pretty that it tries to access the primary db an obviously can not find the tabel. (Which is the error that I receive.) I know how to use the second DB when saving the form or making queries by using the "using" option. I can't figure out how to make this work with the validation though.
I misread you're question. What you are looking for is a database router: https://docs.djangoproject.com/en/3.0/topics/db/multi-db/#automatic-database-routing
You configure these to route some requests to your second database. You could use the router show in the django docs but instead of route_app_labels = {'auth', 'contenttypes'} you would use: route_app_labels = {'yourapp', 'registration'} (and change the relevant reference to your DB too).
I designed and built a Django 1.6.2 survey application using a SessionWizardView which is connected to a MySQL database.
The problem is that (as far as I can see) the submitted form data is not getting saved to the database. This is my first time building an application like this or even working with a database.
Could someone take a look at what I have done and my code and point out any mistakes I have made as to why I cannot see any content submitted by my survey form?
The last time I posted a similar question I was told I needed to create a Model for the data and it was suggested I use a ModelForm to create the table and columns in the database. I have done this but I am still not seeing my submitted content
My Process
I created the database in MySQL (Ver 14.14 Distrib 5.6.20) via
Terminal CREATE database django_db; and the tables in it are created
when I run the command python manage.py syncdb.
I can complete my survey both on my local machine and on the public server. No errors and it appears everything works fine
I have setup phpMyAdmin and can see the django_db database and survey_person model. However I can not seem to find any of the data that should be submitted by the form.
I have tried to use the search facility in phpMyAdmin to find any of the form data I submitted but cannot see it
I have exported the database as a .CSV file but it is empty.
If I use the insert facility in phpMyAdmin the data gets saved in the DB and I can return it when I use the search facilities. The same data is also in the CSV file when I export it.
This seem to suggest that I am missing a step somewhere in my application when it comes to submitting content to the DB.
Can anyone tell me where I am going wrong?
My Code
I have tried to limit the below to only relevant code
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_db',
'USER': 'root',
'PASSWORD': 'xxxxxxxxxxxxxxxxx',
'HOST': '127.0.0.1',
#'PORT': '',
}
}
urls.py
url(r'^surveyone/$', SurveyWizardOne.as_view([
SurveyFormA,
SurveyFormB,
SurveyFormC,
....
....
SurveyFormG,
SurveyFormH,
SurveyFormI
])),
forms.py
class SurveyFormA(forms.ModelForm):
birthdate = forms.DateField(widget=extras.SelectDateWidget(years = range(1995, 1900, -1)), required=False)
class Meta:
model = Person
fields = ['sender', 'birthdate', 'sex', 'relationship', 'state']
class SurveyFormB(forms.ModelForm):
class Meta:
model = Person
fields = ['internet_usage', 'smart_phone_ownership', 'smart_phone_usage']
widgets = {'internet_usage' : RadioSelectNotNull,
'smart_phone_ownership' : RadioSelectNotNull,
'smart_phone_usage' : RadioSelectNotNull,
}
class SurveyFormC(forms.ModelForm):
class Meta:
model = Person
fields = ['education', 'wages', 'presentage_savings', 'occupation', 'living']
widgets = {'education' : forms.RadioSelect,
'wages' : forms.RadioSelect,
'presentage_savings' : forms.RadioSelect,
'occupation' : forms.RadioSelect,
'living' : forms.RadioSelect,}
....
....
models.py
sender = models.EmailField(null=True, blank=True, verbose_name='What is your email address?')
birthdate = models.DateField(null=True, blank=True) #overwritten in forms.py so passed no more arguments
SEX = (
('MALE', 'Male'),
('FEMALE', 'Female'))
sex = models.CharField(null=True, blank=True, max_length=100, choices=SEX, verbose_name='What sex are you?')
RELATIONSHIP = (
('SINGLE', "Single"),
('INARELATIONSHIP', "In a relationship"),
('MARRIED', "Married"),
('DIVORCED', "Divorced"),
('SEPARATED', "Separated"),
('WIDOWED', "Widowed"),)
relationship = models.CharField(null=True, blank=True, max_length=100, choices=RELATIONSHIP, verbose_name='What is your relationship status?')
....
....
def __unicode__(self):
return self
views.py
My views.py are the most complex part of my application. Not sure if it necessary to show any but I thought just in case
class SurveyWizardOne(SessionWizardView):
def get_context_data(self, form, **kwargs):
context = super(SurveyWizardOne, self).get_context_data(form, **kwargs)
step = int(self.steps.current)
if step == 0:
self.request.session['path_one_images'] = ['P1D1.jpg', 'P2D2.jpg', 'P3D3.jpg', 'P4D4.jpg', 'P5D5.jpg', 'P6D6.jpg', 'P7D7.jpg', 'P8D8.jpg', 'P9D9.jpg']
self.request.session['instruction_task_one_images'] = ['IT1A.jpg', 'IT1B.jpg', 'IT1C.jpg']
self.request.session['instruction_task_two_images'] = ['IT2A.jpg', 'IT2B.jpg', 'IT2C.jpg']
self.request.session['images'] = []
self.request.session['slider_DV_values'] = []
PATH_ONE_IMAGES = self.request.session.get('path_one_images', [])
images = self.request.session.get('images', [])
slider_DV_values = self.request.session.get('slider_DV_values', [])
INSTRUCTION_TASK_ONE_IMAGES = self.request.session.get('instruction_task_one_images', [])
INSTRUCTION_TASK_TWO_IMAGES = self.request.session.get('instruction_task_two_images', [])
if step in range (0, 27):
self.request.session['path_one_images'] = PATH_ONE_IMAGES
self.request.session['images'] = images
self.request.session['slider_DV_values'] = slider_DV_values
self.request.session['instruction_task_one_images'] = INSTRUCTION_TASK_ONE_IMAGES
self.request.session['instruction_task_two_images'] = INSTRUCTION_TASK_TWO_IMAGES
if step == 0:
instruction_task_first_image = random.choice(INSTRUCTION_TASK_ONE_IMAGES)
context['display_image'] = instruction_task_first_image
elif step == 1:
instruction_task_second_image = random.choice(INSTRUCTION_TASK_TWO_IMAGES)
context['display_image'] = instruction_task_second_image
elif step == 9:
first_image = random.choice(PATH_ONE_IMAGES)
PATH_ONE_IMAGES.remove(first_image)
context['display_image'] = first_image
images.insert(0, first_image)
self.request.session['first_image'] = images[0]
self.request.session.get('first_image')
elif step == 10:
second_image = random.choice(PATH_ONE_IMAGES)
PATH_ONE_IMAGES.remove(second_image)
....
....
return context
def done(self, form_list, **kwargs):
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
phpMyAdmin
A screenshot of the DB from phpMyAdmin. NOTE: Not the hidden fields at the start are for introduction steps in the survey form, code not shown here for brevity.
Submitting data to a model form does not cause it to be saved automatically. If you want save data from a model form to the database, you need to call its save() method. You could do this in the wizard's done() method.
def done(self, form_list, **kwargs):
# I don't know whether this will work, I am not familiar with your forms.
for form in form_list:
form.save()
# Note the docs suggest redirecting instead of rendering a template.
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
I am using django admin to facilitate editing a database. Recently, we have added another database so using the default in DATABASES in the settings file would not make sense any more.
I now have this in my settings.py file:
DATABASES = {
'default': {},
'animal_tracking': {
'ENGINE':'django.db.backends.mysql',
'NAME': 'AnimalTracking',
'USER': 'foo',
'PASSWORD': 'bar',
'HOST': '127.0.0.1',
'PORT': '3306',
},
'animal_information': {
'ENGINE':'django.db.backends.mysql',
'NAME': 'AnimalInformation',
'USER': 'foo',
'PASSWORD': 'bar',
'HOST': '127.0.0.1',
'PORT': '3306',
},
}
My admin.py file contains:
from django.contrib import admin
from animal_tracking.models import at_animal_types, at_animals
# Register your models here.
class AnimalTypesAdmin(admin.ModelAdmin):
# Sets how the fields are displayed in the add / change section.
fieldsets = [
(None, {'fields': ['type', ]}),
]
# Sets what fields to be displayed in the change (view) section.
list_display = ('type', )
# Registers the current model administration to the admin page.
admin.site.register(at_animal_types, AnimalTypesAdmin)
class AnimalsAdmin(admin.ModelAdmin):
# Sets how the fields are displayed in the add / change section.
fieldsets = [
(None, {'fields': ['tracker_id', 'type', ]}),
('Log information (should not change)', {'fields': ['last_log', 'last_bat', 'last_lat', 'last_lon', ], 'classes': ['collapse']}),
]
# Sets what fields to be displayed in the change (view) section.
list_display = ('tracker_id', 'type', 'last_log', 'last_bat', 'last_lat', 'last_lon', )
# Sets what fields to allow filtering by.
list_filter = ['type', ]
# Sets what fields to allow searching by. Use table__field if foreign key.
search_fields = ['tracker_id', 'type__type', ]
# Registers the current model administration to the admin page.
admin.site.register(at_animals, AnimalsAdmin)
Initially, the admin section would connect with the default database. But now that I removed the default and added the other 2 databases, I'm getting the following error:
If I copy the settings of animal_tracking to default, it works. My question therefore is:
How can I specify which database django admin should use?
Thank you!
You'd better specify the default database, if you leave default empty, you should write db routers.
in your admin, you can use any db you like:
class AnimalTypesAdmin(admin.ModelAdmin):
using = 'animal_tracking'
def get_queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super(MultiDBModelAdmin, self).get_queryset(request).using(self.using)
save_model,delete_model,formfield_for_foreignkey,formfield_for_manytomany should also be overridden like this in the example.
I have a simple model and I want to save it using the ModelForm.
Here's the code:
#models.py
class MyArchive(models.Model):
archive_id = models.CharField(max_length = 20, primary_key=True)
description = models.CharField(max_length = 50, blank = True)
archive_file = models.FileField(upload_to = "my_archives/")
#views.py
class MyArchiveForm(ModelForm):
class Meta:
model = MyArchive
def upload(request):
if request.method == 'POST':
form = MyArchiveForm(request.POST, request.FILES)
if form.is_valid():
form.save()
return HttpResponse('uploaded success!')
else:
logger.debug("invalid form")
return HttpResponse('upload fail!')
I've synced DB and saw the tables created. But every time it goes to form.save then says
DatabaseError, no such table.
Is my way to save using ModelForm wrong?
UPDATE:
What's even weird that when I removed this line: form.save(), it fails at if form.is_valid() with the same error no such table, but when I run django in debug mode, if form.is_valid() works fine.
It's a little trick, turns out led by bad database configuration, I was using relative path in settings.py, so it didn't find the db file at running time, but when run django sycdb, it can find where the db loc.
'default' : {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'sqlite.db', # Or path to database file if using sqlite3.
}
UPDATE:
To use relative path on db file, I should have db setting like this:
from os.path import dirname, join
PROJECT_DIR = dirname(__file__)
DATABASES = {
# ...
'NAME': join(PROJECT_DIR, 'sqlite.db'),
# ...
}
see Can I make the Django database path (for sqlite3) "cross-platform"?