Django accessing multiple databases dynamically - django

I have a django project where I am trying to make a customer based grocery system. Here I have different databases for each customer.
Each database consist of order history as well as all other details like contact details etc.
I will get an url argument to identify customer through customer_id & I will able to get which database to use ie db name. Code -
Model -
class Company(models.Model):
company_id = models.IntegerField(primary_key = True)
name = models.CharField(max_length=256, blank=True, null=True)
db_name = models.CharField(max_length=200, blank=True, null=True)
db_uuid = models.CharField(max_length=100, blank=True, null=True)
objects = DataFrameManager()
def __str__(self):
return self.name
Views file -
#api_view(['GET', 'POST'])
def page(request, uuid):
if request.method == 'GET':
rtrn = Company.objects.filter(db_uuid=uuid).values('db_name')
return Response(rtrn)
Url file -
urlpatterns = [
url(r'user/(?P<uuid>[a-zA-Z0-9-]+)/$', page, name="getfromurl"),
]
Here I will return db name.
But I am not able to understand and implement the change of database. Let me make you understand with an example -
Lets suppose Customer name Gaurav has db 'gaurav_groceries'. By the code I get the name - gaurav_groceries. But in setting.py I have added default db as 'grocies', so how can I use this dynamic nature of db ie in this case we have 'gaurav_groceries', for further calculation and coding.
Thank you

What you are trying to do isn't trivially achieved. You may want to check out existing solutions, such as Django-multitenant which is currently the standard library used for building apps with tenanted DBs in Django.

Related

Django models for astra datastax db

I have developed a website on Django. Initially, I used Django's default Database which is Sqlite3. Now I want to use Astra Datastax DB which is Cassandra. I am not able to convert Django.dB - models into Cassandra.cqlengine - columns function.
I have searched on the Internet and didn't find appropriate documents which could help me.
from django.db import models
from django.contrib.auth import get_user_model
from datetime import datetime
import uuid
User = get_user_model()
class Profile(models.Model):
"""docstring for Profile."""
usr: str = models.ForeignKey(User, on_delete=models.CASCADE)
id_usr: int = models.IntegerField()
Fname:str = models.TextField(blank=True,null=True)
Mname:str = models.TextField(blank=True,null=True)
Lname:str = models.TextField(blank=True,null=True)
Fhone:int = models.IntegerField(blank=True,null=True)
bio: str = models.TextField(blank=True)
img_profile = models.ImageField(
upload_to='ProfileIMG', default="blankprofile.png")
location: str = models.CharField(max_length=250)
def __str__(self):
return self.usr.username
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, unique=True)
user: str = models.CharField(max_length=100)
image = models.ImageField(upload_to="img_posts")
caption: str = models.TextField(max_length=250)
created_at = models.DateTimeField(default=datetime.now)
Likes: int = models.IntegerField(default=0)
def __str__(self):
return self.user
class LikePost(models.Model):
postid: str = models.CharField(max_length=100)
username: str = models.CharField(max_length=100)
def __str__(self):
return self.username
class Followers(models.Model):
follower: str = models.CharField(max_length=100)
user: str = models.CharField(max_length=100)
def __str__(self):
return self.user
Specially, I want to convert this into Cassandra language.
img_profile = models.ImageField( upload_to='ProfileIMG', default="blankprofile.png")
The documentation you want to consult is here: https://docs.datastax.com/en/developer/python-driver/3.25/api/cassandra/cqlengine/columns/
These are the available columns for DjangoCassandraModel, which is what you would use instead of django.db.models to get a model backed by Cassandra. You can see a basic example of connecting to DataStax Astra with django_cassandra_engine here: https://github.com/DataStax-Examples/django-cassandra-blog
As for the ImageField, I am not sure of the Django internals here, but I believe it stores a path to the file in the database after putting it on disk at the location specified ("img_posts" in your example).
You could do the same for a Cassandra backed model, or use the Blob column type to store the image data itself. There are a number of articles and examples of doing this out there.
To add to the above answer, the django_cassandra_engine package does not offer anything with the same level of automation as the ImageField found in Django's models (i.e. storing the uploaded image on local disk and saving the string path to the database, all from the field definition in the model).
What you would do is to work at a slightly lower abstraction level with an explicit (Django, pure) form. This would allow you to manually handle the file upload as outlined here: https://docs.djangoproject.com/en/4.1/ref/forms/fields/#filefield .
Once you have saved the file and have the string path to it, you can create ann istance of the corresponding Model and manually save it -- all in the appopriate view function.
As a side note, your original (sqlite-backed) code makes use of foreign keys and "on delete cascade" provision for removing related rows from other tables. This cannot be transported as is to a Cassandra storage, since the database, by itself, does not support the concept of relational integrity. You would have to decide how to handle these deletes and act on them explicitly in your code.

how to build query with several manyTomany relationships - Django

I really don't understand all the ways to build the right query.
I have the following models in the code i'm working on. I can't change models.
models/FollowUp:
class FollowUp(BaseModel):
name = models.CharField(max_length=256)
questions = models.ManyToManyField(Question, blank=True, )
models/Survey:
class Survey(BaseModel):
name = models.CharField(max_length=256)
followup = models.ManyToManyField(
FollowUp, blank=True, help_text='questionnaires')
user = models.ManyToManyField(User, blank=True, through='SurveyStatus')
models/SurveyStatus:
class SurveyStatus(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
survey = models.ForeignKey(Survey, on_delete=models.CASCADE)
survey_status = models.CharField(max_length=10,
blank=True,
null=True,
choices=STATUS_SURVEY_CHOICES,
)
models/UserSurvey:
class UserSurvey(BaseModel):
user = models.ForeignKey(User, null=True, blank=True,
on_delete=models.DO_NOTHING)
followups = models.ManyToManyField(FollowUp, blank=True)
surveys = models.ManyToManyField(Survey, blank=True)
questions = models.ManyToManyField(Question, blank=True)
#classmethod
def create(cls, user_id):
user = User.objects.filter(pk=user_id).first()
cu_quest = cls(user=user)
cu_quest.save()
cu_quest._get_all_active_surveys
cu_quest._get_all_followups()
cu_quest._get_all_questions()
return cu_quest
def _get_all_questions(self):
[[self.questions.add(ques) for ques in qstnr.questions.all()]
for qstnr in self.followups.all()]
return
def _get_all_followups(self):
queryset = FollowUp.objects.filter(survey__user=self.user).filter(survey__user__surveystatus_survey_status='active')
# queryset = self._get_all_active_surveys()
[self.followups.add(quest) for quest in queryset]
return
#property
def _get_all_active_surveys(self):
queryset = Survey.objects.filter(user=self.user,
surveystatus__survey_status='active')
[self.surveys.add(quest) for quest in queryset]
return
Now my questions:
my view sends to the create of the UserSurvey model in order to create a questionary.
I need to get all the questions of the followup of the surveys with a survey_status = 'active' for the user (the one who clicks on a button)...
I tried several things:
I wrote the _get_all_active_surveys() function and there I get all the surveys that are with a survey_status = 'active' and then the _get_all_followups() function needs to call it to use the result to build its own one. I have an issue telling me that
a list is not a callable object.
I tried to write directly the right query in _get_all_followups() with
queryset = FollowUp.objects.filter(survey__user=self.user).filter(survey__user__surveystatus_survey_status='active')
but I don't succeed to manage all the M2M relationships. I wrote the query above but issue also
Related Field got invalid lookup: surveystatus_survey_status
i read that a related_name can help to build reverse query but i don't understand why?
it's the first time i see return empty and what it needs to return above. Why this notation?
If you have clear explanations (more than the doc) I will very appreciate.
thanks
Quite a few things to answer here, I've put them into a list:
Your _get_all_active_surveys has the #property decorator but neither of the other two methods do? It isn't actually a property so I would remove it.
You are using a list comprehension to add your queryset objects to the m2m field, this is unnecessary as you don't actually want a list object and can be rewritten as e.g. self.surveys.add(*queryset)
You can comma-separate filter expressions as .filter(expression1, expression2) rather than .filter(expression1).filter(expression2).
You are missing an underscore in surveystatus_survey_status it should be surveystatus__survey_status.
Related name is just another way of reverse-accessing relationships, it doesn't actually change how the relationship exists - by default Django will do something like ModelA.modelb_set.all() - you can do reverse_name="my_model_bs" and then ModelA.my_model_bs.all()

limiting __in lookup in django

i have are question about "__in" lookup in Django ORM.
So here is the example of code:
tags = [Tag.objects.get(name="sometag")]
servers = Server.objects.filter(tags__in=tags)[offset_from:offset_to]
server_infos = []
for server in servers:
server_infos.append(query.last())
So here is the problem: we making about 60-70 sql requests for each server. But i want to do something like this:
tags = [Tag.objects.get(name="sometag")]
servers = Server.objects.filter(tags__in=tags)[offset_from:offset_to]
server_infos = ServerInfo.objects.filter(contains__in=servers)
assert servers.count() == server_infos.count()
Can i do this without raw sql request? All i need to understand is how to limit "__in" expression in Django to get only last value as in example above. Is it possible?
Update, my models:
class Tag(models.Model):
name = models.CharField(max_length=255, blank=True)
added_at = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
class Server(models.Model):
ip = models.CharField(max_length=255, blank=True)
port = models.IntegerField(blank=True)
name = models.CharField(max_length=255, blank=True)
tags = models.ManyToManyField(Tag)
added_at = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
def get_server_online(self):
query = ServerInfo.objects.filter(contains=self)
if query.exists():
return query.last().online
return 0
class ServerInfo(models.Model):
contains = models.ForeignKey(Server, \
on_delete=models.CASCADE, null=True, blank=True)
map = models.CharField(max_length=255, blank=True)
game = models.CharField(max_length=255, blank=True)
online = models.IntegerField(null=True)
max_players = models.IntegerField(null=True)
outdated = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag)
ping = models.IntegerField(null=True)
def __str__(self):
return f"Current map {self.map} and current online {self.online}/{self.max_players}"
I think the problem is that tags is ManyToMany and a server may be selected twice by two different tags. Also that a server may have >1 serverinfos, because it's a ForeignKey relation not a OneToOne.
Possibilities:
Make sure a server is returned only once in the queryset:
servers = Server.objects.filter(tags__in=tags).distinct()[offset_from:offset_to]
(or distinct('pk') ?)
Make sure only one ServerInfo instance is returned per server:
server_infos = ServerInfo.objects.filter(contains__in=servers).distinct('contains')
Or use prefetch_related in the Servers query, and then avoid subsequent queries by always referring to the serverinfo objects through the related name (default "serverinfo_set")
I absolutely hate "magic" default related names, and would always code one explicitly: contains = models.ForeignKey(Server, ..., related_name='server_infos', ...)
servers = Server.objects.filter(tags__in=tags).distinct(
).prefetch_related('serverinfo')[offset_from:offset_to]
for server in servers:
server_info = server.serverinfo_set.first()
# or
for info in server.serverinfo_set:
NB don't start applying filters to serverinfo_set if you don't want to hit the DB N times. Filter by iterating through what is presumably a short list, and which is already in memory in the queryset anyway.
The following should retrieve the data using two queries, one to get all the servers and the second one to get all the server infos using prefetch_related(). When filtering on a ManyToManyField you have to use distinct() to avoid duplicate results.
servers = Server.objects.filter(
tags__name="sometag").distinct().prefetch_related("server_info_set")
for server in servers:
print(server.name)
for info in server.server_info_set.all():
print(info)
If you only want to retrieve one certain info per server you would have to provide a custom queryset to prefetch_related() using Prefetch() objects.
from django.db.models import Prefetch
servers = Server.objects.filter(
tags__name="sometag").distinct().prefetch_related(Prefetch('server_info_set',
queryset=ServerInfo.objects.filter(outdated=False),
to_attr="current_infos"
)
)
for server in servers:
print(server.name)
for info in server.current_infos.all():
print(info)

django cms plugin that display model with specific value checked

I made a model that displays articles and when you create an article you have the possibility to choose if this article will be a featured one.
So this is basically what I have in my Article model :
class Article(ModelMeta, TranslatableModel):
taints_cache = True
"""
Press article element,
"""
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
date_realization = models.DateField(_('Realised in'),
default=timezone.now)
image = FilerImageField(verbose_name=_('Featured image'), blank=True,
null=True,
on_delete=models.SET_NULL,
related_name='image_press_article',
help_text=_('Set if the article will be featured'))
sources = models.ManyToManyField(ArticleSource, verbose_name=_('Source'),
blank=False, null=True, related_name='sources_press_article')
regions = models.ManyToManyField(Country, verbose_name=_('Country of the article'),
blank=True, null=True,
related_name='regions_press_article')
global_regions = models.BooleanField('Global', default=True)
featureArticle = models.BooleanField(_('Feature'), help_text=_('Feature this article'), default=False)
Then, I created a plugin that displays the featured articles.
But the thing is, in the django plugin admin I let the user the possibility to choose which article he wants to display (with a maximum of 3).
But in this choosing list, all my articles are listed.
What I want to, is to list only the articles that are checked as "featured", in my plugin admin. Instead of having all the articles.
Here what I have with my cms_plugin's model :
class FeaturedArticlePlugin(CMSPlugin):
selected_article = SortedManyToManyField(Article, blank=True, verbose_name=_('Selected articles'),
help_text=_('Select the featured articles to display'))
def __str__(self):
return u'%s Selected articles' % self.selected_article.all()
def copy_relations(self, oldinstance):
self.selected_article = oldinstance.selected_article.all()
And in my cms_plugins.py :
class PressPlugin(CMSPluginBase):
module = 'Press'
class PressFeaturedArticlePlugin(PressPlugin):
module = _('Press')
name = _('Press feature')
model = FeaturedArticlePlugin
render_template = 'djangocms_press/plugins/feature_article.html'
number_article = 3
def render(self, context, instance, placeholder):
"""
Get a list of selected_articles
"""
selected_article = instance.selected_article.all()
number_selected_article = selected_article.count()
feature_article_list = list(selected_article[:self.number_article])
context['instance'] = instance
context['feature_article_list'] = feature_article_list
return context
plugin_pool.register_plugin(PressFeaturedArticlePlugin)
So, I am sure it's nothing complicated but I can't point this out.
Anyone has a clue ?
EDIT
From what I understand, the only thing that concern the display of all articles is this line :
selected_article = SortedManyToManyField(Article, blank=True, verbose_name=_('Selected articles'),
help_text=_('Select the featured articles to display'))
So what I am suppose to do is to filter this selected_article with the featureArticle=True. But how to do it ?
I am not quite sure if I am missing something, but, couldn't you just apply a filter here?
selected_article = instance.selected_article.all().filter(featureArticle=true)
number_selected_article = selected_article.count()
Or is the problem with the lines after?
feature_article_list = list(selected_article[:self.number_article])
If your problem is selecting the extra articles, maybe you need to order them by date and select only the necessary?
feature_article_list = list(Articles.all().order_by('-created')[:self.number_article - number_selected_article]
Which will only select the extra necessaries?
Edit: Your situation kind of reminds me of a problem I once had. So I'll refer you to the same page that helped me in the past just in case you'd manage to figure it out.
Restrict django admin change permissions
Edit 2 : "I created a plugin that displays the featured articles. But the thing is, in the django plugin admin I let the user the possibility to choose which article he wants to display (with a maximum of 3). But in this choosing list, all my articles are listed."
Isn't it ok if all the articles are displayed there? How can you choose among them if they are not all displayed?

Django user audit

I would like to create a view with a table that lists all changes (created/modified) that a user has made on/for any object.
The Django Admin site has similar functionality but this only works for objects created/altered in the admin.
All my models have, in addition to their specific fields, following general fields, that should be used for this purpose:
created_by = models.ForeignKey(User, verbose_name='Created by', related_name='%(class)s_created_items',)
modified_by = models.ForeignKey(User, verbose_name='Updated by', related_name='%(class)s_modified_items', null=True)
created = CreationDateTimeField(_('created'))
modified = ModificationDateTimeField(_('modified'))
I tried playing around with:
u = User.objects.get(pk=1)
u.myobject1_created_items.all()
u.myobject1_modified_items.all()
u.myobject2_created_items.all()
u.myobject2_modified_items.all()
... # repeat for >20 models
...and then grouping them together with itertool's chain(). But the result is not a QuerySet which makes it kind of non-Django and more difficult to handle.
I realize there are packages available that will do this for me, but is it possible to achieve what I want using the above models, without using external packages? The required fields (created_by/modified_by and their timefields) are in my database already anyway.
Any idea on the best way to handle this?
Django admin uses generic foreign keys to handle your case so you should probably do something like that. Let's take a look at how django admn does it (https://github.com/django/django/blob/master/django/contrib/admin/models.py):
class LogEntry(models.Model):
action_time = models.DateTimeField(_('action time'), auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.TextField(_('object id'), blank=True, null=True)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'))
change_message = models.TextField(_('change message'), blank=True)
So, you can add an additional model (LogEntry) that will hold a ForeignKey to the user that changed (added / modified) the object and a GenericForeignKey (https://docs.djangoproject.com/en/1.7/ref/contrib/contenttypes/#generic-relations) to the object that was modified.
Then, you can modify your views to add LogEntry objects when objects are modified. When you want to display all changes by a User, just do something like:
user = User.objects.get(pk=1)
changes = LogEntry.objects.filter(user=user)
# Now you can use changes for your requirement!
I've written a nice blog post about that (auditing objects in django) which could be useful: http://spapas.github.io/2015/01/21/django-model-auditing/#adding-simple-auditing-functionality-ourselves