Ok, I've been crawling google and Django documentation for over 2 hours now (as well as the IRC channel on freenode), and haven't been able to figure this one out.
Basically, I have a model called Room, which is displayed below:
class Room(models.Model):
"""
A `Partyline` room. Rooms on the `Partyline`s are like mini-chatrooms. Each
room has a variable amount of `Caller`s, and usually a moderator of some
sort. Each `Partyline` has many rooms, and it is common for `Caller`s to
join multiple rooms over the duration of their call.
"""
LIVE = 0
PRIVATE = 1
ONE_ON_ONE = 2
UNCENSORED = 3
BULLETIN_BOARD = 4
CHILL = 5
PHONE_BOOTH = 6
TYPE_CHOICES = (
('LR', 'Live Room'),
('PR', 'Private Room'),
('UR', 'Uncensored Room'),
)
type = models.CharField('Room Type', max_length=2, choices=TYPE_CHOICES)
number = models.IntegerField('Room Number')
partyline = models.ForeignKey(Partyline)
owner = models.ForeignKey(User, blank=True, null=True)
bans = models.ManyToManyField(Caller, blank=True, null=True)
def __unicode__(self):
return "%s - %s %d" % (self.partyline.name, self.type, self.number)
I've also got a forms.py which has the following ModelForm to represent my Room model:
from django.forms import ModelForm
from partyline_portal.rooms.models import Room
class RoomForm(ModelForm):
class Meta:
model = Room
I'm creating a view which allows administrators to edit a given Room object. Here's my view (so far):
def edit_room(request, id=None):
"""
Edit various attributes of a specific `Room`. Room owners do not have
access to this page. They cannot edit the attributes of the `Room`(s) that
they control.
"""
room = get_object_or_404(Room, id=id)
if not room.is_owner(request.user):
return HttpResponseForbidden('Forbidden.')
if is_user_type(request.user, ['admin']):
form_type = RoomForm
elif is_user_type(request.user, ['lm']):
form_type = LineManagerEditRoomForm
elif is_user_type(request.user, ['lo']):
form_type = LineOwnerEditRoomForm
if request.method == 'POST':
form = form_type(request.POST, instance=room)
if form.is_valid():
if 'owner' in form.cleaned_data:
room.owner = form.cleaned_data['owner']
room.save()
else:
defaults = {'type': room.type, 'number': room.number, 'partyline': room.partyline.id}
if room.owner:
defaults['owner'] = room.owner.id
if room.bans:
defaults['bans'] = room.bans.all() ### this does not work properly!
form = form_type(defaults, instance=room)
variables = RequestContext(request, {'form': form, 'room': room})
return render_to_response('portal/rooms/edit.html', variables)
Now, this view works fine when I view the page. It shows all of the form attributes, and all of the default values are filled in (when users do a GET)... EXCEPT for the default values for the ManyToMany field 'bans'.
Basically, if an admins clicks on a Room object to edit, the page they go to will show all of the Rooms default values except for the 'bans'. No matter what I do, I can't find a way to get Django to display the currently 'banned users' for the Room object. Here is the line of code that needs to be changed (from the view):
defaults = {'type': room.type, 'number': room.number, 'partyline': room.partyline.id}
if room.owner:
defaults['owner'] = room.owner.id
if room.bans:
defaults['bans'] = room.bans.all() ### this does not work properly!
There must be some other syntax I have to use to specify the default value for the 'bans' field. I've really been pulling my hair out on this one, and would definitely appreciate some help.
Thanks!
UPDATE
lazerscience actually helped me find the solution in one of his comments. Basically, the way it works is if you pass a list of primary keys. To make it work I had to change:
if room.bans:
defaults['bans'] = room.bans.all() ### this does not work properly!
to
if room.bans:
defaults['bans'] = [b.pk for b in room.bans.all()]
And bam, it instantly started working (when I view the page, it will show a selectable list of Callers, with the already banned callers already highlighted (selected).
You probably need to use "initial": Django set default form values
Related
I am trying to make a warehouse management system with Django 3.2 based on this models:
class itemtype(models.Model):
item_id = models.IntegerField(primary_key=True)
item_name = models.CharField(max_length=100)
group_name = models.CharField(max_length=100)
category_name = models.CharField(max_length=100)
mass = models.FloatField()
volume = models.FloatField()
used_in_storage = models.BooleanField(default=False, null=True)
class Meta:
indexes = [
models.Index(fields=['item_id'])
]
def __str__(self):
return '{}, {}'.format(self.item_id, self.item_name)
class material_storage(models.Model):
storage_id = models.AutoField(primary_key=True)
material = models.ForeignKey(itemtype, on_delete=models.PROTECT)
amount_total = models.IntegerField(null=True)
price_avg = models.FloatField(null=True)
order_amount = models.IntegerField(null=True)
order_price = models.IntegerField(null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
return '{}, {} avg.: {} ISK'.format(self.material, self.amount, self.price)
"itemtype" defines basically the possible objects which could be stored and "material_storage" shows what is in stock. I tried to combine the total amount of every item as well as the average price paid for it and the amount and price for a single order in the same database row. The idea is to get the last record for the chosen item/material when a new order happens, add the amount of that order and recalculate the avg price.
Theoretically this could be split up on two tables, but I don't see a reason to do so at the moment.
However, I am not able to figure out the actual function code to do the calculations. I am new to Django and therefor a bit overwhelmed by the complexity. I tried to use class based views and model forms, for the easy stuff that worked fine but now I am kind of lost.
Making a form just for adding new rows to that storage table was ok.
class NewAssetForm(forms.ModelForm):
material = MaterialChoiceField(models.itemtype.objects.filter(used_in_storage= True))
def __init__(self, *args, **kwargs):
super(NewAssetForm, self).__init__(*args, **kwargs)
self.fields['amount'].widget.attrs['min'] = 1
self.fields['price'].widget.attrs['min'] = 1
class Meta:
model = models.material_storage
fields = (
'material',
'amount',
'price'
)
widgets = {
'material': forms.Select(),
}
Same for the View to process it.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
return super().form_valid(form)
But now I am stuck. I thought this should be a fairly standard task, but I couldn't find a solution for it by now. The Idea was to put it in the form_valid function, take the material from the form to find the latest relevant record, add the new amount as well as calculate the new average price and save all together to the model. So far i only found a few examples comparable with my problem at all and I wasn't able to translate them to my setup, so maybe someone can give me a hint for a more successful search or provide me an example how to approach this topic.
thx in advance.
To modify the values of the form fields, you can override "clean" method and provide values to the form fields. Data can be accessed using "self.cleaned_data", it is a dictionary.
class NewAssetForm(ModelForm):
def clean(self):
super().clean()
# place code that retrieves existing data and calculate new values.
self.cleaned_data['price'] = 'New Value'
cleaned_data will be passed to "form_valid", there you can call the save function. "form.save()" will create a new row, make sure you are passing valid values to the views. Since you are accepting few fields in the form, make sure you have default values for the fields that are not included in the form object.
Thank you for your answer I found a solution by using the form_valid() method within the FormView. The majority of the code is used to create entries based on the existing entries or to check whether there are already entries for the same material.
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
try:
# check if there is already a record for this material.
material_storage.objects.filter(material_id = form.cleaned_data['material'])[:1]
# getting total amount and average price values from latest entry with said material.
total_amount = material_storage.objects.values('amount_total').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['amount_total']
avg_price = material_storage.objects.values('price_avg').filter(material_id=form.cleaned_data['material']).order_by('-timestamp')[:1][0]['price_avg']
amount = form.cleaned_data['amount']
price = form.cleaned_data['price']
# calculating new total amount and average price based on old values and new entry.
form.instance.amount_total = total_amount + amount
form.instance.price_avg = ((avg_price * total_amount) + (price * amount)) / (total_amount + amount)
form.save()
except material_storage.DoesNotExist:
# if there is no entry for the chosen material yet, amount = total amount, price = average price.
form.instance.amount_total = form.cleaned_data['amount']
form.instance.price_avg = form.cleaned_data['price']
form.save()
return super().form_valid(form)
For now this solves my problem, however I don't know if the chosen location (form_valid()) makes sense - your answer suggests it would make more sense elsewhere.
Also, checking if an entry already exists for the material and selecting values from such an entry are pretty sure not very elegant and efficient. But as already mentioned, I am a beginner - I would be happy about any suggestions for improvement.
I am also not sure yet if this handles every probable special case which could appear...
I have 2 models employee and leave, where Employee is the foreign key in Leave. I want to display the leave requests by a specific employee on a page when they log in.
i'm not able to get the leave data populated on, and if there are more than 2 leaves applied by a single employee i get an error saying 2 items found
here is my code
models.py
class Employee(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
no_of_leaves=models.IntegerField(
null=False,
validators=[
MinValueValidator(1),
MaxValueValidator(24)
],
default=24
)
def __str__(self):
return self.user.username
class Leave(models.Model):
employee=models.ForeignKey(Employee,on_delete=models.CASCADE,default="")
start_date=models.DateField(auto_now_add=False)
end_date=models.DateField(auto_now_add=False)
req_date=models.DateTimeField(default=datetime.datetime.now())
STATUS_OPTIONS = (
("Approve","Approve"),
("Pending","Pending"),
("Decline","Decline"),
)
approved=models.CharField(max_length=10,choices=STATUS_OPTIONS,default='Pending')
def __str__(self):
return self.employee.user.username
#property
def date_diff(self):
return (self.start_date - self.end_date).days
views.py
def home(request):
user = request.user
u = User.objects.get(username=user)
e = Employee.objects.get(user=user.id)
leave = Leave.objects.get_or_create(employee=e)
print(leave)
nofleaves=None
if user.is_superuser:
pass
else:
nofleaves=u.employee.no_of_leaves
context={'nofleaves':nofleaves,'leave':leave,}
return render(request,"leaveApp/home.html",context)
Just like #Jao said get is to fetch only one data while there is a many to one relationship between Leave and Employee that means there can be multiple Leave for one employee thus get will throw an error.
You should use filter:
Change this line
leave = Leave.objects.get_or_create(employee=e) to something like
leave = Leave.objects.filter(employee=e.id)
if not leave.exists():
leave = Leave.objects.create(employee=e)
This other question might help.
The thing is you should use get for only one data. You shouldn't use get on a many-to-one relathionship. The logic is, there can be multiple Leave by Employee so you can't have a consistent use of get.
What you can use is filter but I'd use related names/related fields which will allow you to use employee.leaves if you define it correctly.
Unable to perform the update operation.
This function is for table update, but the code is not allowing me to pass the instance in the form because it is not a model form. Please suggest the changes.
class userForm(forms.Form):
SHIFT_CHOICES = ( ('D','DAY'), ('N','NIGHT') )
ADMISSION_FORM_STATUS = ( ('Y','YES'), ('N','NO') )
FORM_COMPLETE_STATUS = ( ('Y','YES'), ('N','NO'))
TRAINING_STATUS = ( ('Y','YES'), ('N','NO') )
STATUS = ( ('W','WORKING'), ('OL','ON_LEAVE'), ('E','EXIT') )
employee_id = forms.CharField(max_length=8,required=False)
employer_id = CompanyModelChoiceField(required=False, queryset=Company.objects.all(), label='Employer', widget=Select2Widget)
name = forms.CharField(max_length=255)
uber_name = forms.CharField(max_length=255, required=False)
mobile = forms.CharField(max_length=20, required=False)
To bind data with the form fields, (not a model form), you will have to pass a dictionary with those specific fields to the form constructor.
In this case what you need to do is
Get the form data in a dictionary
driver = Driver.objects.get(employee_id = employee_id)
form_data = {'employee_id':driver.employee_id, 'employer_id', driver.employer_id, .., .., .} #all the fields in the form
AddDriverForm(initial=form_data)
Then while saving the data you should do it the same way you did for creating the driver.
Moreover, I would personally suggest you to use Model form for this case since you are going to put that data in the model anyways, it will surely save you the hassle.
Also, you might consider to normalize your model if its not a big deal.
Lets say your model (DC) has an instance (an entry):
Superhero = Bruce, Butler = Alfred, Engineer = Lucius, car = Batmobile
Your form has
Superhero, Butler, Engineer
(Notice that my form is missing the car field)
Now, suppose you want to change the name Butler to Jarvis
instance = DC.objects.get(id=8) #lets consider this as the id of the instance we have
#what you need to do is save the required data in a dictionary in this case
form_data = {'Superhero':instance.Superhero, 'Butler':instance.Butler, 'Engineer': instance.Engineer } #we haven't passed the car field
#Also I think that passing extra fields must not be an issue but I haven't tried
UpdateForm(form_data)
This will render the data properly when the form is displayed.
I am trying to figure out how to dynamically change a ModelForm field based on the input from a previous field.
For example, if I have these kinds of models:
class Phone(models.Model):
name = models.CharField(max_length=10)
class Series(models.Model):
name = models.CharField(max_length=10)
class Manufacturer(models.Model):
phone = models.ForeignKey('Phone')
series = models.ForeignKey('Series')
class ManufacturerForm(ModelForm):
class Meta:
model = Manufacturer
Which would generate a form (ManufacturerForm) with dropdown options for the phone and series entries in the database. Is it possible to a different set of series entries based on the phone entered by the user, for example?
I have read about using the __init__ method to accomplish this, based on what I have read on this blog post, but I am not sure how to execute this given my scenario. Or maybe there is a better way to go about achieving this that you have taken? Thanks for any advice!
EDIT: Added the form's view.
def make_ad(request):
if request.method == 'POST':
form = ManufacturerForm(request.POST, request.FILES)
if form.is_valid():
a = form.save()
a.user = request.user
a.save()
else:
form = ManufacturerForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('place.html', variables)
#super9 mentioned using ajax request to change these elements. I need to check if request.is_ajax(), but at what point should I check this in my view? And how do I add or change the queryset based on the ajax request?
Thanks for your advice.
EDIT: Trying to use django-smart-selects
Not sure how to setup my models to utilize django-smart-selects to accomplish what I am trying. Here is how I have structured my models:
from smart_selects.db_fields import ChainedForeignKey
class Phone(models.Model):
phone = models.CharField(max_length=10)
class Series(models.Model):
series = models.CharField(max_length=10)
phone = models.ForeignKey(Phone)
class SeriesModel(models.Model):
model = models.CharField(max_length=10)
series = models.ForeignKey(Series)
class Manufacturer(models.Model):
phone = models.ForeignKey(Phone)
series = ChainedForeignKey (Series, chained_field = "phone", chained_model_field = "phone")
series_model = ChainedForeignKey (SeriesModel, chained_field = "series", chained_model_field = "series")
But when I view my form (ModelForm) the fields for series_model are not chained properly to series. Am I missing something to make smart-selects work on the second layer of abstraction?
EDIT: Above code now works.
I am working on a library system to manage certain items in our office, I don't need a full-blown integrated library system so I decided to hand roll one with Django.
Below is a simplified version of my model:
class ItemObjects(models.Model):
# Static Variables
IN_STATUS = 'Available'
OUT_STATUS = 'Checked out'
MISSING = 'Missing'
STATUS_CHOICES = (
(IN_STATUS, 'Available'),
(OUT_STATUS, 'Checked out'),
(MISSING, 'Missing'),
)
# Fields
slug = models.SlugField(unique=True)
date_added = models.DateField(auto_now_add=True)
last_checkin = models.DateTimeField(editable=False, null=True)
last_checkout = models.DateTimeField(editable=False, null=True)
last_activity = models.DateTimeField(editable=False, null=True)
status = models.CharField(choices=STATUS_CHOICES, default=IN_STATUS, max_length=25)
who_has = models.OneToOneField(User, blank=True, null=True)
times_out = models.PositiveIntegerField(default=0, editable=False)
notes = models.CharField(blank=True, max_length=500)
history = models.TextField(blank=True, editable=False)
pending_checkin = models.BooleanField(default=False)
pending_transfer = models.BooleanField(default=False)
At first I was using a method on ItemObject to process checking out an item to a user and who_has was an EmailField because I couldn't get a CharfField to populate with the logged in user's name, but I figured using a OneToOneField is probably closer to the "right" way to do this.. While who_has was an EmailField, the following method worked:
def check_out_itemobject(self, user):
user_profile = user.get_profile()
if self.status == 'Available' and self.who_has == '':
self.status = 'Checked out'
self.who_has = user.email
self.last_checkout = datetime.datetime.now()
self.last_activity = datetime.datetime.now()
self.times_out += 1
if self.history == '':
self.history += "%s" % user_profile.full_name
else:
self.history += ", %s" % user_profile.full_name
if user_profile.history == '':
user_profile.history += self.title
else:
user_profile.history += ", %s" % self.title
else:
return False # Not sure is this is "right"
user_profile.save()
super(ItemObjects, self).save()
Now that I am using a OneToOneField this doesn't work, so I started looking at using a subclass of ModelForm but none of the cases I saw here on SO seemed to apply for what I am trying to do; my form would be a button, and that's it. Here are some of the questions I looked at:
Django: saving multiple modelforms simultaneously (complex case)
(Django) (Foreign Key Issues) model.person_id May not be NULL
django update modelform
So was I on the right track with a sort of altered save() method, or would a ModelForm subclass be the way to go?
EDIT/UPDATE: Many thanks to #ChrisPratt!
So I am trying to get Chris Pratt's suggestion for showing ItemHistory to work, but when I try to render it on a page I get an AttributeError that states "'User' object has no attribute 'timestamp'". So my question is, why is it complaining about a User object when last_activity is an attribute on the ItemObject object ?
My view:
#login_required
def item_detail(request, slug):
item = get_object_or_404(Item, slug=slug)
i_history = item.last_activity
user = request.user
return render_to_response('items/item_detail.html',
{ 'item' : item,
'i_history': i_history,
'user' : user })
I do not see why a User object is coming up at this point.
EDIT2: Nevermind, history is clearly a M2M field whose target is User. That's why!
Assuming users will log in and check out books to themselves, then what you most likely want is a ForeignKey to User. A book will only have one User at any given time, but presumably Users could check out other items as well. If there is some limit, even if the limit is actually one per user, it would be better to validate this in the model's clean method. Something like:
def clean(self):
if self.who_has and self.who_has.itemobject_set.count() >= LIMIT:
raise ValidationError('You have already checked out your maximum amount of items.')
Now, you checkout method has a number of issues. First, status should be a defined set of choices, not just random strings.
class ItemObject(models.Model):
AVAILABLE = 1
CHECKED_OUT = 2
STATUS_CHOICES = (
(AVAILABLE, 'Available'),
(CHECKED_OUT, 'Checked Out'),
)
...
status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=AVAILABLE)
Then, you can run your checks like:
if self.status == self.STATUS_AVAILABLE:
self.status = self.STATUS_CHECKED_OUT
You could use strings and a CharField instead if you like, as well. The key is to decouple the static text from your code, which allows much greater flexibility in your app going forward.
Next, history needs to be a ManyToManyField. Right now, your "history" is only who last checked the item out or what the last item the user checked out was, and as a result is pretty useless.
class ItemObject(models.Model):
...
history = models.ManyToManyField(User, through='ItemHistory', related_name='item_history', blank=True)
class ItemHistory(models.Model):
CHECKED_OUT = 1
RETURNED = 2
ACTIVITY_CHOICES = (
(CHECKED_OUT, 'Checked Out'),
(RETURNED, 'Returned'),
)
item = models.ForeignKey(ItemObject)
user = models.ForeignKey(User)
activity = models.PostiveIntegerField(choices=ACTIVITY_CHOICES)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-timestamp'] # latest first
Which then allows you to get full histories:
some_item.history.all()
some_user.item_history.all()
To add a new history, you would do:
ItemHistory.objects.create(item=some_item, user=some_user, activity=ItemHistory.CHECKED_OUT)
The auto_now_add attribute ensures that the timestamp is automatically set when the relationship is created.
You could then actually get rid of the last_checkout and last_activity fields entirely and use something like the following:
class ItemObject(models.Model):
...
def _last_checkout(self):
try:
return self.history.filter(activity=ItemHistory.CHECKED_OUT)[0].timestamp
except IndexError:
return None
last_checkout = property(_last_checkout)
def _last_activity(self):
try:
return self.history.all()[0].timestamp
except IndexError:
return None
last_activity = property(_last_activity)
And, you can then use them as normal:
some_item.last_checkout
Finally, your checkout method is not an override of save so it's not appropriate to call super(ItemObject, self).save(). Just use self.save() instead.