Report building in Django - django

I am trying to build a system which will enable my users to create their own reports based on the model of their choosing without me having to code them every time they need updating.
In order to do this, I've come up with the following models:-
from django.db import models
from django.contrib.contenttypes.models import ContentType
class ReportField(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
data_method = models.CharField(max_length=100)
def get_value_for(self, object):
return getattr(object, self.data_method)
class Report(models.Model):
name = models.CharField(max_length=200)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
data_fields = models.ManyToManyField(ReportField)
The idea is that users can create a Report based on the model they're interested in. They can then add any number of ReportFields to that report and, when the report runs, it will call the data_method (the name of a property) on each instance of the model in the db.
The bit I'm having trouble with is defining which properties the users can have access to. I need to have a way of creating a load of ReportFields with certain data_methods for each model. But I don't want to create them by hand - I want it to work in a similar way to the way Permissions work in Django, if that's possible, like this:-
class MyModel(models.Model):
class Meta:
data_methods = (
('property_name_1', 'Property Name 1'),
('property_name_2', 'Property Name 2'),
etc.
)
From reading the source code, Django seems to run a management command after every migration on that model to make sure the model permissions are created. Is that the only way to do this? Am I going in the right direction here, or is there a better way?

Related

Create a history record in Django for every UPDATE of a class instance

I have a model in Django that is used to create an item of stock
class Item(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
description = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='item')
amount = models.IntegerField(default=0, blank=False)
place = models.ForeignKey(Place, on_delete=models.CASCADE, related_name='place')
issue_amount = models.IntegerField(default=0, blank=True)
receive_amount = models.IntegerField(default=0, blank=True)
The item amount will be updated everytime an item is issued by adding or subtracting the issue or receive amount in a views function that alters the instance amount and calls save on that instance.
I want to be able to keep a record of every update and make this information available to both my frontend and to an admin model.
I found this tutorial in which the poster creates a separate model with the same field values as Item and then writes SQL commands directly to the database to create a TRIGGER that saves each Item field on every update: https://www.youtube.com/watch?v=d26DUXynf8s
Is there a way I can replicate this same behaviour using Django?
You want an audit log library.
There are a few (I've never been completely satisfied with any of them) but I quite like this one.
As you can see in the docs, you register your model for auditing like so...
from django.db import models
from auditlog.registry import auditlog
class MyModel(models.Model):
pass # Model definition goes here as usual.
auditlog.register(MyModel) # Register the model.
...and then you can access the log for a particular row via the new history property.
log = MyModel.objects.first().history.latest()
You can browse the various other Django audit log options here, and they are all more or less variations on the same theme.
Django already has this exact feature you're looking for with the LogEntry model. All you need to do is to read the data from the Database.
django.contrib.admin.LogEntry

In Django, does the intermediate model need to belong in the same file as the target model?

When running a migration, I am currently getting this lazy reference ValueError whenever the model that I have a defined ManyToManyField is in a separate file from the model that I am targeting. However, when I place the two models in the same file, I am able to successfully run the mirgation.
Why is this error happening?
Is it possible to separate the models (community.py and community_member.py) into separate files?
ValueError: contains a
lazy reference to fitness.communitymember, but app 'fitness' doesn't
provide model 'communitymember'.
My directory structure looks like this:
my_app
|----fitness
|----user.py
|----community.py
|----community_member.py
community.py
class Community(models.Model):
id = models.AutoField(
primary_key=True,
)
owner = models.ForeignKey(
User,
)
members = models.ManyToManyField(
User,
through='CommunityMember',
through_fields=('community', 'member')
)
class Meta:
db_table = 'Communities'
community_member.py
class CommunityMember(models.Model):
community = models.ForeignKey(
Community,
db_column='community_id'
)
member = models.ForeignKey(
User,
db_column='member_id',
)
class Meta:
db_table = 'Community_Members'
settings.py
INSTALLED_APPS = [
'my_app.fitness'
]
If I place the contents of community_members.py inside of community.py the migration is successfully executes, but I am not sure why.
Rashed, I really don't know why you need to split your models, but if you insisted in doing so, there must be a reason. A default Django's app must include a models.py file. The ValueError comes from the fact Django is unable to find all your models definitions, it simple doesn't know where to look for these models. If you insist in doing so (which I personally don't recommend with only two models) you should do what the docummentation recommends: https://docs.djangoproject.com/en/1.11/topics/db/models/#organizing-models-in-a-package

Django many-to-many lookup from different models

I have some models that represents some companies and their structure. Also all models can generate some Notifications (Notes). User can see own Notes, and, of course, can't see others.
class Note(models.Model):
text = models.CharField(...)
class Company(models.Model):
user = models.ForeignKey(User)
note = models.ManyToManyField(Note, blank='True', null='True')
class Department(models.Model):
company = models.ForeignKey(Company)
note = models.ManyToManyField(Note, blank='True', null='True')
class Worker(models.Model):
department = models.ForeignKey(Department)
note = models.ManyToManyField(Note, blank='True', null='True')
class Document(models.Model)
company = models.ForeignKey(Company)
note = models.ManyToManyField(Note, blank='True', null='True')
The question is how I can collect all Notes for particular user to show them?
I can do:
Note.objects.filter(worker__company__user=2)
But its only for Notes that was generated by Workers. What about another? I can try hardcoded all existing models, but if do so dozen of kittens will die!
I also tried to use backward lookups but got "do not support nested lookups". May be I did something wrong.
EDIT:
As I mentioned above I know how to do this by enumerating all models (Company, Worker, etc. ). But if I will create a new model (in another App for example) that also can generate Notes, I have to change code in the View in another App, and that's not good.
You can get the Notes of a user by using the following query:
For example let us think that a user's id is 1 and we want to keep it in variable x so that we can use it in query. So the code will be like this:
>>x = 1
>>Note.objects.filter(Q(**{'%s_id' % 'worker__department__company__user' : x})|Q(**{'%s_id' % 'document__company__user' : x})|Q(**{'%s_id' % 'company__user' : x})|Q(**{'%s_id' % 'department__company__user' : x})).distinct()
Here I am running OR operation using Q and distinct() at the end of the query to remove duplicates.
EDIT:
As I mentioned above I know how to do this by enumerating all models
(Company, Worker, etc. ). But if I will create a new model (in another
App for example) that also can generate Notes, I have to change code
in the View in another App, and that's not good.
In my opinion, if you write another model, how are you suppose to get the notes from that model without adding new query? Here each class (ie. Department, Worker) are separately connected to Company and each of the classes has its own m2m relation with Note and there is no straight connection to User with Note's of other classes(except Company). Another way could be using through but for that you have change the existing model definitions.
Another Solution:
As you have mentioned in comments, you are willing to change the model structure if it makes your query easier, then you can try the following solution:
class BaseModel(models.Model):
user = models.Foreignkey(User)
note = models.ManyToManyField(Note)
reports_to = models.ForeignKey('self', null=True, default=None)
class Company(BaseModel):
class Meta:
proxy = True
class Document(BaseModel):
class Meta:
proxy = True
#And so on.....
Advantages: No need to create separate table for document/company etc.
object creation:
>>c= Company.objects.create(user_id=1)
>>c.note.add(Note.objects.create(text='Hello'))
>>d = Document.objects.create(user_id=1, related_to=c)
>>d.note.add(Note.objects.create(text='Hello World'))

Django model design: editable help text for individual model fields. Is there a foreign field that references a specific field of a model?

I have several models with several fields in my app. I want to set up a way for the user to be able to modify a help text system for each field in the model. Can you give me some guidance on how to design the models, and what field types to use? I don't feel right about storing the model and field name in CharFields, but if that is the only way, I may be stuck with it.
Is there a more elegant solution using Django?
For a quick and silly example, with an app named jobs, one named fun, and make a new app named helptext:
jobs.models.py:
class Person(models.Model):
first_name = models.CharField(max_length=32)
.
.
interests = models.TextField()
def __unicode__(self):
return self.name
class Job(models.Model):
name = models.CharField(max_length=128)
person = models.ForeignKey(Person)
address = models.TextField()
duties = models.TextField()
def __unicode__(self):
return self.name
fun.models.py:
class RollerCoaster(models.Model):
name = models.CharField(max_length=128)
scare_factor = models.PositiveInteger()
def __unicode__(self):
return self.name
class BigDipper(RollerCoaster):
max_elevation = models.PositiveInteger()
best_comment_ever_made = models.CharField(max_length=255)
def __unicode__(self):
return super.name
Now, let's say I want to have editable help text on Person.interests, and Job.duties, RollerCoaster.scare_factor, and BigDipper.best_comment_ever_made. I'd have something like:
helptext.models.py:
from django.contrib.contenttypes.models import ContentType
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255)
helptext = models.CharField(max_length=128)
def __unicode__(self):
return self.helptext
So, what is the better way to do this, other than making HelpText.the_model and HelpText.the_field CharFields that have to be compared when I am rendering the template to see if helptext is associated with each field on the screen?
Thanks in advance!
Edit:
I know about the help_text parameter of the fields, but I want this to be easily edited through the GUI, and it may contain a LOT of help with styling, etc. It would be HTML with probably upwards of 50-60 lines of text for probably 100 different model fields. I don't want to store it in the field definition for those reasons.
I changed the HelpText model to have a reference to ContentType and the field a CharField. Does this seem like a good solution? I am not sure this is the most elegant way. Please advise.
Edit 2013-04-19 16:53 PST:
Currently, I tried this and it works, but not sure this is great:
from django.db import models
from django.contrib.contenttypes.models import ContentType
# Field choices for the drop down.
FIELDS = ()
# For each ContentType verify the model_class() is not None and if not, add a tuple
# to FIELDS with the model name and field name displayed, but storing only the field
# name.
for ct in ContentType.objects.all():
m = ct.model_class()
if m is not None:
for f in ct.model_class()._meta.get_all_field_names():
FIELDS += ((f, str(ct.model) + '.' + str(f)),)
# HelpText model, associated with multiple models and fields.
class HelpText(models.Model):
the_model = models.ForeignKey(ContentType)
the_field = models.CharField(max_length=255, choices=FIELDS)
helptext = models.TextField(null=True, blank=True)
def __unicode__(self):
return self.helptext
Doesn't feel like the best, but please advise if this is a solution that will bite me in the behind later on and make me filled with regrets... :*(
The solution works, and I have it implemented, but you have to be aware that sometimes the ContentTypes get out of sync with your models. You can manually update the content types with this:
python manage.py shell
>>> from django.contrib.contenttypes.management import update_all_contenttypes
>>> update_all_contenttypes(interactive=True)
This allows you to add the new ones and remove the old ones, if they exist.
The nice thing about the Field not being a foreign key is that I can put anything in it for help text. So, say I have a field "First Name." I can put a helptext connected to the Person model and the "first_name" field. I can also make something up, like "Something really confusing." The helptext is now associated with the Person model and the "Something really confusing" field. So, I can put it at the top of the form, instead of associating to a field with hard foreign keying. It can be anything arbitrary and will follow with that "field" anywhere. The hangup would be that you may change the name of the helptext field association inadvertently sending your original helptext into never land.
To make this easy, I created a TemplateTag, which I pass the name of the model and the name of the "field" I want to associate. Then anytime the template is rendered, that helptext is there, editable for anybody to get assistance with their user interface forms.
Not sure this is the best solution, but I couldn't really see any other way to do it, and got no responses.
Cheerio!

ForeignKey to multiple Models or Queryset

It is possible to make a ForeignKey to more than one model. I want to choose from different models like Parts and Machines Model.
I read this to combine multiple models into one list: How to combine 2 or more querysets in a Django view?
How can I get foreign key to that list somehow?
I know that you asked this over year ago, but I had a similar problem and I want to share a link to the solution for future readers.
Generally the contenttypes framework solves this problem, and I guess this is what Daniel Roseman was talking about.
How to use dynamic foreignkey in Django?
You need generic relations.
A generic relation allows you to dynamically the target model of the foreign key.
I'll provide a comprehensive answer for this question, I know its quite old, but it's still relevant.
We're gonna be using Generic Relations.
First, in settings.py make sure that django.contrib.contenttypes is included in the INSTALLED_APPS array.
Let's create a new model in models.py:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
With content_type we can associate Image with any other model class, while object_id will hold the other model instance.
class Image(models.Model):
image = models.ImageField(
upload_to="imgs/products", blank=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
To refer back to the Image model from a Company instance we need to make a reverse generic relation
class Company(models.Model):
name = models.CharField(max_length=100)
images = GenericRelation(Image)
In schema.py, we can create Images in a Company instance like:
company_instance = Company(name="Apple")
company_instance.save()
for img in imgs:
#Image(image=img, content_object=company_instance)
company_instance.images.create(image=img)
company_instance.images.all() # fetch all images
the company_instance.images field is just a GenericRelatedObjectManager (docs)
This is how the final Image table looks in the database:
The Django-polymorphic library provides a simple solution that should work well with the admin and forms too using formsets.
For example:
from polymorphic.models import PolymorphicModel
class BookOwner(PolymorphicModel):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class StaffBookOwner(BookOwner):
owner = models.ForeignKey(Staff, on_delete=models.CASCADE)
class StudentBookOwner(BookOwner):
owner = models.ForeignKey(Student, on_delete=models.CASCADE)
With this, you can use the parent model to set the owner to either a Staff or Student instance or use the child models directly.