I am working with fields in a model in which the value of a field depend of a selected option in a previous field in a form (for the moment via django administrator), by which I am using django-smart-selects
I would share this video for a better understanding about of my question or situation and the reason about of ask the way of apply DEBUG or depuration in relation with the fields which I am working in this scenario.
The parent field Segmento afectado in the video let me select multiple choices
The child field Movimiento in the video, detail the movements that a affected segment (selected in the parent field) can perform
My situation about it
In the video is detailed of a way clear that when I select only a affected segment in the parent field, the options of movements are deployed in the child field, and my log console of Django Server appear the status code HTTP/1.1 200 with the GET operation, indicating to me that was possible get and deploy successfully the movements that correspond to the affected segment selected in the parent field
But, when I select more than one affected segment in the parent field (If I select two or more segments in forward), inmediately my django server at the console show me the status code HTTP/1.1 404 (Not Found in yellow color), because not perform the second selection or the second affected segment that the user select, and due to this reason also don't deploy or appear in the child field the movements associated that these second affected segment can perform
I don't know how to address this situation, due to this form in which are the parent (Segmento Afectado) and child (Movimiento) fields are represented inside the django administrator.
I have a model named AffectedSegment and another model named Movement and through of functionality of ChainedManyToManyField of django-smart-selects is the way in how to I get the values deployed in the child field accord to the selection in the parent field.
My models and the chaining of values for this behavior are:
class AffectedSegment(models.Model):
SEGMENTO_ESCAPULA = 'ESCAPULA'
SEGMENTO_HOMBRO = 'HOMBRO'
SEGMENTO_CODO = 'CODO'
SEGMENTO_ANTEBRAZO = 'ANTEBRAZO'
SEGMENTO_CARPO_MUNECA = 'CARPO_MUNECA'
SEGMENTO_MANO = 'MANO'
SEGMENTO_CHOICES = (
(SEGMENTO_ESCAPULA, u'Escápula'),
(SEGMENTO_HOMBRO, u'Hombro'),
(SEGMENTO_CODO, u'Codo'),
(SEGMENTO_ANTEBRAZO, u'Antebrazo'),
(SEGMENTO_CARPO_MUNECA, u'Carpo/Muñeca'),
(SEGMENTO_MANO, u'Mano'),
)
affected_segment = models.CharField(max_length=12, choices=SEGMENTO_CHOICES, blank=False, verbose_name='Segmento afectado')
class Meta:
verbose_name = 'Segmentos corporale'
def __str__(self):
return "%s" % self.affected_segment
class Movement(models.Model):
type = models.CharField(max_length=255,verbose_name='Tipo de movimiento')
corporal_segment_associated = models.ManyToManyField(AffectedSegment, blank=False, verbose_name='Segmento corporal asociado')
class Meta:
verbose_name = 'Movimiento'
def __str__(self):
return "%s" % self.type
And the way in which or I get that appear the movements in the child field accord to the selection on the parent field is perform of this way:
class RehabilitationSession(models.Model):
affected_segment = models.ManyToManyField(AffectedSegment,verbose_name='Segmento afectado')
movement = ChainedManyToManyField(
Movement, #Modelo encadenado
chained_field = 'affected_segment',
chained_model_field = 'corporal_segment_associated',
verbose_name='Movimiento'
)
class Meta:
verbose_name = 'Sesiones de Rehabilitación'
def __str__(self):
return "%s" % self.affected_segment
I don't know how to can I address this situation.
What another alternatives can I have?
JS, some framework forntend?
Another django app?
Any support, orientation about it, will be highly appreciated
:D
Just by briefly looking to your video and the code of "django-smart-select" application, I can see that this application was not meant to do what you are trying.
When you select only one option, the ending of your url looks like this:
"../RehabilitationSession/movement/4/"
But when you select two options, your url looks like this:
"../RehabilitationSession/movement/4,5/"
and then you get the 404 error.
Reason for you error lies in the fact that the application "django-smart-select" don't have an appropriate url pattern to deal with "4,5" block. For this to work, it would need to accept comma-separated integers and then have some kind of parser implemented in the app to filter your results by multiple conditions.
I cannot give you the complete answer, but I can tell you that you have few options:
If you have the skill, you can try and extend the mentioned app and implement the parser and url patterns yourself.
You can do the same thing in your admin class by overriding the query of your field based on the selection of the above field, but without jQuery tinkering, it wont have real-time changes.
You can try some other application, but I cannot guarantee that you will find a suitable solution for your problem.
Related
I have a custom contact form for which I create a sent_time field using auto_now_add to save the time when the user had sent the message.
I am able to list all the information on the listing view of the admin panel however when I try to enter a specific message I hit the following error:
'sent_time' cannot be specified for GeneralContact model form as it is a non-editable field
My attempt to make the fields readonly in the ModelAdmin results in the same error
class GeneralContactAdmin(ModelAdmin):
"""
Admin model for general correspondence via
the main contact form on the information page
"""
model = GeneralContact
list_display = GeneralContact.__all__
search_fields = GeneralContact.__all__
readonly_fields = GeneralContact.__all__
ordering = ('-sent_time',)
list_filter = ('sent_time', 'has_response')
Surely it is possible to be displayed only, perhaps I've done something incorrectly in my models?
Here is the base model I use for the contact model
class ContactFormBase(models.Model):
__all__ = (
'sent_time', 'sender_name', 'sender_email',
'sender_message', 'has_response', 'responded_on'
)
sent_time = models.DateTimeField(auto_now_add=True)
sender_name = models.CharField()
sender_email = models.EmailField()
sender_message = models.TextField()
has_response = models.BooleanField(
default=False,
help_text='Select whether this message has been replied to by an admin.',
)
responded_on = models.DateTimeField(blank=True, null=True)
panels = [
FieldRowPanel([
FieldPanel('sender_name'),
FieldPanel('sender_email'),
]),
FieldPanel('sent_time'),
FieldPanel('sender_message'),
FieldRowPanel([
FieldPanel('has_response'),
FieldPanel('responded_on'),
])
]
class Meta:
abstract = True
ordering = ['-sent_time',]
The actual class being used is rather plain, perhaps something needs to be done here to allow display of readonly fields?
class GeneralContact(ContactFormBase, models.Model):
panels = ContactFormBase.panels
class Meta:
verbose_name = 'General Contact Entry'
verbose_name_plural = 'General Contact Entries'
In the list view all the information is able to be displayed. In the editing view, ideally there would be all of the information about the message and sender as readonly fields and an option for the admin to change the has_response value based on whether someone has responded or not.
In what way could I achieve this?
update
After seeing this Q&A I have changed the auto_now_add to use django.utils.timezone.now as the default on the sent_time attribute and life seems better, the error from the start of the question is gone and the edit view loads up entirely. However, now all the fields are editable which is not desirable.
Looking into the ModelAdmin class provided by Wagtail it appears that readonly_fields isn't available and perhaps only a feature of the django admin class of the same name. So I'm not sure what to do here. Wagtails HelpPanel type of output is what I'm looking for, and I had an idea to use that to display the data but I'm not sure what that looks like or even how it'd be done as I'm just learning django and wagtail.
update 2
Attempted to use HelpPanel instead of FieldPanel in order to try display the values but seems as if the HelpPanel doesn't retrieve the value of the attributes. Checking through these docs I see the mention of things like djangos readonly_field is not included which confirms why one of my former attempts didn't work but I did find mention of inspect_view_enabled which displays the values in a read only fashion and after trying it out it looks very much how I was trying to get it, alas, nothing there is editable which makes sense but I am getting closer.
I am wondering if a good solution would be to override the view or template used for GeneralContactAdmin but unsure if that's the right way to go about it just to output some text for one class.
A simpler solution is to keep the inspect view and only add the has_response to the edit view, but two views, one of which would only be a checkbox is not a nice for UX.
Surely there is a better way to solve this?
I have a model:
class Registration(models.Model):
student_name = models.CharField(max_length=50)
season = models.ForeignKey(Season, on_delete=models.CASCADE)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
address = models.TextField()
class Meta:
unique_together = (('student_name', 'selected_season', 'selected_subject'),)
I want to create a form for this model where the user will be given the option to select the subject based on the season that they selected. I have models for them as well:
class Subject(models.Model):
subject_name = models.CharField(max_length=50)
...
class Season(models.Model):
season = models.CharField(max_length=2, primary_key=True)
subject = models.ManyToManyField(Subject)
...
I dont know how to query the Form. Should It be a ModelForm or a regular Form? How to query the database in the form?
You can only know which season was selected when the form is submitted, so there's no simple direct way to implement this (note that this is a HTTP limitation, not a django one). IOW you'll need either a "wizard" process or front-end scripting.
The "wizard" solution is: one first form where the user selects the season, user submits the form, your code selects the related subjects and displays a second form with subjects choices, user selects subjects and submits for final validation (nb: this is usually done within a single view, using a form hidden field to keep track of the current step and which season was selected in first step). This is garanteed to work (if correctly implemented of course xD), but not really user friendly.
Second solution is to use front-end scripting.
In it's simplest form, when the user selects the season, you use js to hide other seasons subjects (or you first hide all subjects and only display relevant ones when the season is selected). This can be done rather simply by grouping all subjects for a given season in a same fieldset (or whatever other container tag) with an id matching the season's one, or by having a distinct html "select" (with same name but different ids) per season. Of course you can also have all subjects in one single html select (or whatever), keep a front-side (js) mapping of seasons=>subjects, and update your select or whatever from this mapping.
This solution can be made to work (in "degraded" mode) without JS (you just have to make sure the subjects selector(s) are visible and active by default). You'll have to implement a custom validation backend-side (cf django's forms doc for this) to make sure the subject matches the season anyway (never trust front-side validation, it's only user-friendly sugar), so in the worst case the form will just not validate.
Now if you have a LOT of seasons and subjects, you may want to prefer doing an ajax query when the user selects the season and populate the subjects selector from this query's result (to avoid initially rendering a huge list of options), but then you can't make it work without JS anymore (whether this is an issue or not depends on your requirements).
EDIT
If i do follow the form wizard option, I need to create 2 forms, the first consisting of just the Season.
Well, you could do it with one single form (passing an argument to specify what should be done) but using two forms is much simpler indeed.
The 2nd form will consist of the rest of the options (except for seasons), am I right? So should the 2nd form be a modelform?
Well, that's the point of modelforms, isn't it ?
How do I put a queryset in the ModelChoiceField in modelform? I googled but could't find anything –
In your form's __init__, you update self.fields["subject"].queryset with your filtered queryset.
IMPORTANT: do NOT try to touch self.fieldname - this is the class-level field definition, so changing it would totally unexpected results in production (been here, done that, and that was quite some fun to debug xD).
Now this being said, for your use case, I reckon you'd be better with the simple js filtering solution - it's the easiest to set up - on the backend it's totally transparent, and the front-end part is rather simple using jQuery -, it's much more user-friendly, and it's garanteed to work.
Having a lot of models, I have started to factor out common blocks with the aim of my database reaching second normal form. As the application should be used by a sales team, most entries are some kind of orders. An excerpt from my models file looks like this:
class Order(models.Model):
dl = models.CharField(max_length=100)
cl = models.CharField(max_length=100)
(...)
class Setup(models.Model):
order = models.ForeignKey(Order) # could be OneToOneField()
name = models.CharField(max_length=200)
package = models.CharField(choices=(
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
('XL', 'Extra large'),
('C', 'Custom')
), max_length=2)
server = models.ForeignKey(Webserver)
(...)
While it does not make any logical sense to keep the order details out of the model for a setup order, it helps to keep the project maintainable, since setup orders are not the only things coming in and this way you can change the order model and all other models/orders are updated too.
Here comes the problem. For attributes like Setup.server Django's default behaviour of creating a dropdown with all web servers the company can offer is totally fine. If the team decides to add another one, it can simply create another server option on another page. But for order, I would like that the OrderForm is included on the same page as the SetupForm, ideally as a separate Fieldset. After submitting the form, the new order should be added to the database and Setup.order is to be filled. I know that I can code it for this special case, but the application will contain numerous forms, so a generic solution would be better.
A possible solution could be to create a custom models.ForeignKey or models.OneToOneField with a custom option and a generic view that than renders a template with two forms and links the objects afterwards.
Did anyone have a similar problem? Does anyone know a simple, maybe even builtin solution to that?
EDIT 1
I have thought about inline formsets which were the solution to a similar question which used this example
class Contact(models.Model):
...
class Communication(models.Model):
contact = models.ForeignKey(Contact)
and by using a communication fieldset. However, this treats the class containing as the child, which is not the case in my example. But it still is an option, even if it would still have to be automated so it can be used quickly for all links between the other models.
I posted this question on the django-users list, but haven't had a reply there yet.
I have models that look something like this:
class ProductGroup(models.Model):
name = models.CharField(max_length=10, primary_key=True)
def __unicode__(self): return self.name
class ProductRun(models.Model):
date = models.DateField(primary_key=True)
def __unicode__(self): return self.date.isoformat()
class CatalogItem(models.Model):
cid = models.CharField(max_length=25, primary_key=True)
group = models.ForeignKey(ProductGroup)
run = models.ForeignKey(ProductRun)
pnumber = models.IntegerField()
def __unicode__(self): return self.cid
class Meta:
unique_together = ('group', 'run', 'pnumber')
class Transaction(models.Model):
timestamp = models.DateTimeField()
user = models.ForeignKey(User)
item = models.ForeignKey(CatalogItem)
quantity = models.IntegerField()
price = models.FloatField()
Let's say there are about 10 ProductGroups and 10-20 relevant
ProductRuns at any given time. Each group has 20-200 distinct
product numbers (pnumber), so there are at least a few thousand
CatalogItems.
I am working on formsets for the Transaction model. Instead of a
single select menu with the several thousand CatalogItems for the
ForeignKey field, I want to substitute three drop-down menus, for
group, run, and pnumber, which uniquely identify the CatalogItem.
I'd also like to limit the choices in the second two drop-downs to
those runs and pnumbers which are available for the currently
selected product group (I can update them via AJAX if the user
changes the product group, but it's important that the initial page
load as described without relying on AJAX).
What's the best way to do this?
As a point of departure, here's what I've tried/considered so far:
My first approach was to exclude the item foreign key field from the
form, add the substitute dropdowns by overriding the add_fields
method of the formset, and then extract the data and populate the
fields manually on the model instances before saving them. It's
straightforward and pretty simple, but it's not very reusable and I
don't think it is the right way to do this.
My second approach was to create a new field which inherits both
MultiValueField and ModelChoiceField, and a corresponding
MultiWidget subclass. This seems like the right approach. As
Malcolm Tredinnick put it in
a django-users discussion,
"the 'smarts' of a field lie in the Field class."
The problem I'm having is when/where to fetch the lists of choices
from the db. The code I have now does it in the Field's __init__,
but that means I have to know which ProductGroup I'm dealing with
before I can even define the Form class, since I have to instantiate the
Field when I define the form. So I have a factory
function which I call at the last minute from my view--after I know
what CatalogItems I have and which product group they're in--to
create form/formset classes and instantiate them. It works, but I
wonder if there's a better way. After all, the field should be
able to determine the correct choices much later on, once it knows
its current value.
Another problem is that my implementation limits the entire formset
to transactions relating to (CatalogItems from) a single
ProductGroup.
A third possibility I'm entertaining is to put it all in the Widget
class. Once I have the related model instance, or the cid, or
whatever the widget is given, I can get the ProductGroup and
construct the drop-downs. This would solve the issues with my
second approach, but doesn't seem like the right approach.
One way of setting field choices of a form in a formset is in the form's __init__ method by overwriting the self.fields['field_name'].choices, but since a more dynamic approach is desired, here is what works in a view:
from django.forms.models import modelformset_factory
user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices
PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5)
my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices)
# and now for the magical for loop
for choice_form in my_formset:
choice_form.fields['model'].choices = user_choices
I wasn't able to find the answer for this but tried it out and it works in Django 1.6.5. I figured it out since formsets and for loops seem to go so well together :)
I ended up sticking with the second approach, but I'm convinced now that it was the Short Way That Was Very Long. I had to dig around a bit in the ModelForm and FormField innards, and IMO the complexity outweighs the minimal benefits.
What I wrote in the question about the first approach, "It's straightforward and pretty simple," should have been the tip-off.
In my Django admin, when I try to view/edit objects from one particular model class the memory usage and CPU rockets up and I have to restart the server. I can view the list of objects fine, but the problem comes when I click on one of the objects. Other models are fine. Working with the object in code (i.e. creating and displaying) is ok, the problem only arises when I try to view an object with the admin interface. The class isn't even particularly exotic:
class Comment(models.Model):
user = models.ForeignKey(User)
thing = models.ForeignKey(Thing)
date = models.DateTimeField(auto_now_add=True)
content = models.TextField(blank=True, null=True)
approved = models.BooleanField(default=True)
class Meta:
ordering = ['-date']
Any ideas? I'm stumped. The only reason I could think of might be that the thing is quite a large object (a few kb), but as I understand it, it wouldn't get loaded until it was needed (correct?).
It's not really a question of how big the Thing object is, but rather of how many you have in your database. That's because for a ForeignKey, by default Django's admin gives you a drop-down list containing all the existing items. If you've got lots and lots, then Django will load them all in order to populate that list. The same is true here of User.
The best way round this is to add the offending field to the raw_id_fields in your ModelAdmin subclass. That will change the representation to a simple textfield for the id, with a pop-up lookup window.