Adding/deleting fields in the same ModelForm django - django

I have little experience in django so I would really appreciate your help!
In general, I have created a ModelForm which depending on the users who is logged in, he changes some values in the field. So consider that this form is being edited about 5 times by 5 different users.
I would like to show a specific field in the template (and view) only when the third user is logged in.
My model is :
class Task(models.Model):
Taskdetails = models.CharField(max_length=500, null=True)
asset = models.ForeignKey('Asset', null=True)
failure = models.ForeignKey('Failure', null=True)
cause = models.ForeignKey('Cause', null=True)
Created_task_date = models.DateTimeField(default=timezone.now, null=True)
employee = models.ForeignKey("auth.User", null = True)
and also i have created this ModelForm
class Meta:
model = Task
fields = ('Taskdetails', 'asset', 'failure', ,'employee','cause',)
Also I have 5 edititions of the TaskForm in which is user edits something.
The thing I am trying to do is to show the cause field only in the third form.
I tried to exclude the value but nothing apperas.
If i include the field cause (just like above), I must "pass" it in the template in order to be edited from the first user (otherwise the task_form is not saved)
I hope I became clear.
I would really appreciate your help.

Related

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

Django (Model)Form Field: Manytomany with key value pair

I have a situation where I need to do something similar to rendering a formset within a formset. But I'd rather focus on the problem before jumping to a solution.
In English first:
I'm creating a shipment from a warehouse.
Each shipment can contain multiple lines (unique combinations of product_type and package_type) with an item_count
However for each line there could be multiple "Packages" - a package_type of a product_type that has an item_count. Think of this as a batch.
The customer is only interested in seeing one line for each product_type/package_type
But we need to pull out the stock and correctly attribute the particular units from each batch to allow stock control, recall control etc to function. Therefore the dispatch staff IS interested in exactly which Packages are shipped.
Add to this the sales staff enter a SalesOrder that only specifies the product_type/package_type. They aren't interested in the Packages either. (Think putting in a forward order for next month - who knows what will be in stock then?).
Now the models (simplified for clarity):
class Package(models.Model):
create_date = models.DateField()
quantity = models.FloatField()
package_type = models.ForeignKey(PackageType, on_delete=models.PROTECT)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT)
class CheckOut(models.Model):
package = models.ForeignKey(Package, on_delete=models.PROTECT)
create_date = models.DateField()
quantity = models.FloatField()
class Shipment(models.Model):
sales_order = models.ForeignKey(SalesOrder, null=True, blank=True)
ship_date = models.DateField(default=date.today,
verbose_name='Ship Date')
class ShipmentLine(models.Model):
shipment = models.ForeignKey(Shipment, null=True, blank=True)
sales_order_line = models.ForeignKey(SalesOrderLine, null=True, blank=True)
quantity = models.FloatField(verbose_name='Quantity Shipped')
checkout = models.ManytoManyField(CheckOut)
I currently have it working well with the constraint of a 1:M relationship of CheckOut:ShipmentLine. However when changing this to a M:M, things get knarly form-wise.
In the 1:M version the Shipment form (plus formset for the ShipmentLines) looks like this:
class CreateShipmentForm(forms.ModelForm):
class Meta:
model = om.Shipment
contact = forms.ModelChoiceField(
queryset=om.Contact.objects.filter(is_customer=True, active=True),
label='Customer')
customer_ref = forms.CharField(required=False, label='Customer Reference')
sales_order = forms.ModelChoiceField(queryset=om.SalesOrder.objects.all(),
required=False, widget=forms.HiddenInput())
number = forms.CharField(label='Shipment Number', required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
class CreateShipmentLineForm(forms.ModelForm):
class Meta:
model = om.ShipmentLine
widgets = {
'checkout': forms.HiddenInput()
}
fields = ('package', 'quantity', 'id',
'sales_order_line', 'checkout')
id = forms.IntegerField(widget=forms.HiddenInput())
sales_order_line = forms.ModelChoiceField(
widget=forms.HiddenInput(), required=False,
queryset=om.SalesOrderLine.objects.all())
package = forms.ModelChoiceField(required=True, queryset=None) # queryset populated in __init__, removed for brevity
So for the 1:M, I could select a package, set the quantity and done.
For M:M, I will need to select product_type, package_type, and then 1 or more packages, AND for each package a quantity. (I'll be using JS in the form to filter these)
In my mind's eye I have a few possibilities:
create a (child) formset for the Packages and quantities and include in each line of the (parent) formset
create some sort of multi-field, multi-value matrix custom form field and use that
construct a modal dialog where the M:M stuff happens and somehow save the result to the form where validation, saving happens.
I hope I have explained it correctly and clearly enough. It's the most complex application of Django forms I've encountered and I'm not sure what the limitations/pros/cons of each of my options is.
Has anyone encountered this situation and have a solution? Or any words to the wise?
My thanks in advance,
Nathan
I have a similar situation, I am doing something like your second and third options:
I have overridden __init__() and, after calling super, I have a loop that adds a value selector for every field (of course you could use a single custom element here)
Then override save() and after calling super I process the extra field adding all the values.

Django admin list_display not showing several objects

I have just begun to play around with Django admin views, and to start off, I am trying to do something very simple: showing several fields in the listing of objects using list_display as explained here: https://docs.djangoproject.com/en/dev/ref/contrib/admin/
This is my dead simple code:
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'category')
Unfortunately, the list_display option is causing the columnar view to appear, but only some of the objects (40 out of 85) are now displaying in the listing. I cannot deduce why certain objects are showing over the others - their fields look like they are filled similarly. It's clearly not paginating, because when I tried it on an admin of another model, it showed only 2 objects out of about 70 objects.
What might be going on here?
[UPDATE] Article Model:
class Article(models.Model):
revision = models.ForeignKey('ArticleRevision', related_name="current_revision")
category = models.ForeignKey('meta.Category')
language = models.ForeignKey('meta.Language', default=get_default_language)
created = models.DateTimeField(auto_now_add=True, editable=False)
changed = models.DateTimeField(auto_now=True, editable=False)
title = models.CharField(max_length=256)
resources = models.ManyToManyField('oer.Resource', blank=True)
image = models.ManyToManyField('media.Image', blank=True)
views = models.IntegerField(editable=False, default=0)
license = models.ForeignKey('license.License', default=get_default_license)
slug = models.SlugField(max_length=256)
difficulty = models.PositiveIntegerField(editable=True, default=0)
published = models.NullBooleanField()
citation = models.CharField(max_length=1024, blank=True, null=True)
Before adding list_display:
After adding list_display:
[UPDATE] This behaviour occurs only when ForeignKey fields are included in list_display tuple. Any of them.
[UPDATE] Category model code:
class Category(models.Model):
title = models.CharField(max_length=256)
parent = models.ForeignKey('self')
project = models.NullBooleanField(default=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
slug = models.SlugField(max_length=256, blank=True)
def __unicode__(self):
return self.title
This behavior is caused by a foreign key relation somewhere that is not declared as nullable, but nonetheless has a null value in the database. When you have a ManyToOne relationship in list_display, the change list class will always execute the query using select_related. (See the get_query_set method in django.contrib.admin.views.ChangeList).
select_related by default follows all foreign keys on each object, so any broken foreign key found by this query will cause data to drop out when the query is evaluated. This is not specific to the admin; you can interactively test it by comparing the results of Article.objects.all() to Article.objects.all().select_related().
There's no simple way to control which foreign keys the admin will look up - select_related takes some parameters, but the admin doesn't expose a way to pass them through. In theory you could write your own ChangeList class and override get_query_set, but I don't recommend that.
The real fix is to make sure your foreign key model fields accurately reflect the state of your database in their null settings. Personally, I'd probably do this by commenting out all FKs on Article other than Category, seeing if that helps, then turning them back on one by one until things start breaking. The problem doesn't have to be with a FK on an article itself; if a revision, language or category has a broken FK that will still cause the join to miss rows. Or if something they relate to has a broken FK, etc etc.

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)

How do you set the initial value for a ManyToMany field in django?

I am using a ModelForm to create a form, and I have gotten the initial values set for every field in the form except for the one that is a ManyToMany field.
I understand that I need to give it a list, but I can't get it to work. My code in my view right now is:
userProfile = request.user.get_profile()
employer = userProfile.employer
bar_memberships = userProfile.barmembership.all()
profileForm = ProfileForm(
initial = {'employer': employer, 'barmembership' : bar_memberships})
But that doesn't work. Am I missing something here?
Per request in the comments, here's the relevant parts of my model:
# a class where bar memberships are held and handled.
class BarMembership(models.Model):
barMembershipUUID = models.AutoField("a unique ID for each bar membership",
primary_key=True)
barMembership = USStateField("the two letter state abbreviation of a bar membership")
def __unicode__(self):
return self.get_barMembership_display()
class Meta:
verbose_name = "bar membership"
db_table = "BarMembership"
ordering = ["barMembership"]
And the user profile that's being extended:
# a class to extend the User class with the fields we need.
class UserProfile(models.Model):
userProfileUUID = models.AutoField("a unique ID for each user profile",
primary_key=True)
user = models.ForeignKey(User,
verbose_name="the user this model extends",
unique=True)
employer = models.CharField("the user's employer",
max_length=100,
blank=True)
barmembership = models.ManyToManyField(BarMembership,
verbose_name="the bar memberships held by the user",
blank=True,
null=True)
Hope this helps.
OK, I finally figured this out. Good lord, sometimes the solutions are way too easy.
I need to be doing:
profileForm = ProfileForm(instance = userProfile)
I made that change, and now everything works.
Although the answer by mlissner might work in some cases, I do not think it is what you want. The keyword "instance" is meant for updating an existing record.
Referring to your attempt to use the keyword "initial", just change the line to:
bar_memberships = userProfile.barmembership.all().values_list('pk', flat=True)
I have not tested this with your code, but I use something similar in my code and it works.