Hello This is my first question so please forgive the formatting:
Current lab setup
1 project:
library
1 app
catalog
2 databases
library_admin_db (admin-mysql)
catalog_db (mysql1
I'm trying to add a database object on "catalog_db" database using a "CreateView" Class based view
I already set up the Databases connection:
DATABASES = {
# Must use Default
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'library_admin_db',
'USER': 'root',
'PASSWORD': 'password',
'HOST': '192.168.164.128',
'PORT': '8002'
},
'catalog': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'catalog_db',
'USER': 'root',
'PASSWORD': 'password',
'HOST': '192.168.164.128',
'PORT': '8000',
}
}
I set up the DATABASE_ROUTERS:
DATABASE_ROUTERS = [
BASE_DIR / 'routers.db_routers.LibraryRouter'# the "MyApp1Router is the class inside the db_routers file"
]
Here is the Routers class:
class LibraryRouter:
route_app_labels = {'catalog'}
def db_for_read(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'catalog_db'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label in self.route_app_labels:
return 'catalog_db'
return None
def allow_relation(self, obj1, obj2, **hints):
if (
obj1._meta.app_label in self.route_app_labels or
obj2._meta.app_label in self.route_app_labels
):
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
if app_label in self.route_app_labels:
return db == 'catalog_db'
return None
here is my model with the foreign keys:
from django.db import models
from django.urls import reverse
import uuid
# Create your models here.
class Genre(models.Model):
name = models.CharField(max_length=150)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey('Author', on_delete=models.SET_NULL, null=True)
summary = models.TextField(max_length=600)
isbn = models.CharField('ISBN', max_length=13, unique=True)
genre = models.ManyToManyField(Genre)
language = models.ForeignKey('language', on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('book_detail', kwargs={"pk":self.pk})
class Language(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
date_of_birth = models.DateField(null=True,blank=True)
class Meta:
ordering = ['last_name','first_name']
def get_absolute_url(self):
return reverse('author_detail', kwargs={"pk":self.pk})
def __str__(self):
return f"{self.last_name} {self.first_name}"
class BookInstance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
book = models.ForeignKey('Book', on_delete=models.RESTRICT, null=True)
imprint = models.CharField(max_length=200)
due_back = models.DateField(null=True, blank=True)
LOAN_STATUS = (
('m', "Maintenance"),
('o', 'On Loan'),
('a', 'Available'),
('r', 'Reserved')
)
status = models.CharField(max_length=1, choices=LOAN_STATUS, blank=True, default='m')
class Meta:
ordering = ['due_back']
def __str__(self):
return f'{self.id} ({self.book.title})'
Here is the View:
from django.shortcuts import render
from catalog.models import Book, BookInstance, Author, Genre, Language
from django.views.generic import CreateView
from django.urls import reverse_lazy
# Create your views here.
class BookCreateView(CreateView):
model = Book
fields = ['title', 'author', 'summary', 'isbn', 'genre', 'language']
# queryset = Book.objects.using('catalog') # Must use this if using a secondary database! if not using secondary database then this is automated!
success_url = reverse_lazy('catalog:home')
def form_valid(self, form):
temp = form.save(commit=False)
temp.save(using='catalog')
print('Hello')
return super().form_valid(form)
def form_invalid(self, form):
print('form_invalid')
return super().form_invalid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'].fields['author'].queryset = Author.objects.using('catalog')
context['form'].fields['language'].queryset = Language.objects.using('catalog').all()
context['form'].fields['genre'].queryset = Genre.objects.using('catalog').all()
return context
Here is the Jinja template with the form:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Teacher Form</h1>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
</body>
</html>
The Issue comes here on the validation:
[enter image description here][1]
[1]: https://i.sstatic.net/oKD1n.png <- Image of issue
I'm not sure how to validate the information for the create form to the "create_db" I'm almost certain it's checking the "admin_db" but none of the records are stored in the "admin_db" only the "create_db".
I've been searching for hours with no luck as it seems everyone uses a single database and as such there isn't much documentation on support for multiple databases.
The problem is that you are specifying the DATABASE_ROUTERS
incorrectly. The DATABASE_ROUTERS
setting is supposed to be a list of import strings or the database router instance itself, whereas you are passing a list of pathlib.Path
objects:
DATABASE_ROUTERS = [
BASE_DIR / 'routers.db_routers.LibraryRouter'# the "MyApp1Router is the class inside the db_routers file"
]
You coincidentally don't get any error for this because of a few reasons, firstly Django's implementation [GitHub] is just assuming that you are passing a router instance to it:
for r in self._routers: if isinstance(r, str): router = import_string(r)() else: router = r
Next when it comes to actually using the instance it just assumes that the router passed doesn't implement the specific method as seen from the code [GitHub]:
for router in self.routers: try: method = getattr(router, action) except AttributeError: # If the router doesn't have a method, skip to the next one. pass
So in simpler words your router isn't actually being used and you should update your settings to pass a proper import string:
DATABASE_ROUTERS = [
'routers.db_routers.LibraryRouter' # the "MyApp1Router is the class inside the db_routers file"
]