Saturday, July 29, 2017

Re: Need help modifying form on intermediate page of admin action

I looked over those apps and I don't think they'll help with my issue.

Let's say that Crate 1 has a capacity of 12 and currently holds 10 jars.  If I look at the jars in Crate 2, select 3 of them, and then trigger the admin action to move the selected jars, Crate 1 should *not* be in the drop down list on the intermediate form because it only has room for 2 more jars.  There isn't really a value entered by the user, it's just the number of jars that are selected before the admin action is triggered that matters.

I do not know how to modify the value of dest in an unbound instance of MoveMultipleJarsForm based on the length of the queryset.  As a workaround, I think I could do some validation in move_multiple_jars() that would check the capacity of the destination and generate an error message if the destination can't hold all the selected jars, but that seems inelegant.

Jack.

On Saturday, July 29, 2017 at 10:24:02 PM UTC-7, mark wrote:
You might find one of these helpful - 



Basically, you want a "chained select" that is populated after a value is entered by the user, if I understand the problem. If you want to roll your own, then you need something like ajax or node.js to populate the crate select once you know how many jars you want to ship. 

Another way to think about it, is to use a validation on the crate object to make sure there is room. This solution may not be as elegant, but does not need any additional coding outside of django. 

Good luck!

Mark

On Sat, Jul 29, 2017 at 8:57 PM, Jack Twilley <mat...@gmail.com> wrote:
I have a Django app which keeps inventory.  There are two relevant classes here, one named Jar and another named Crate.  The Jar class has an attribute crate which is a foreign key for the Crate class.  The Crate class has an attribute capacity of type models.IntegerField which represents the maximum number of Jar objects that it can hold.  The Crate class also has a property "jars" which returns "self.jar_set.filter(is_active=True).count()" or in other words, the number of Jar objects with is_active set to True that are associated with that Crate object.

This code is from models.py and shows the two object classes:

class Crate(models.Model):
    number
= models.IntegerField()
    slug
= models.SlugField(max_length=10, unique=True, blank=True)
    capacity
= models.IntegerField(default=12)
   
# bin where the crate can currently be found
    bin
= models.ForeignKey(Bin)


    created_at
= models.DateTimeField(auto_now_add=True)
    updated_at
= models.DateTimeField(auto_now=True)


   
class Meta:
        ordering
= ['number']


   
@property
   
def jars(self):
       
return self.jar_set.filter(is_active=True).count()


   
@property
   
def shortname(self):
       
return "C%d" % self.number


   
@property
   
def longname(self):
       
return "Crate %d" % self.number


   
@property
   
def name(self):
       
return self.longname


   
def __str__(self):
       
return self.name


   
def save(self, *args, **kwargs):
       
self.slug = 'c%d' % self.number
       
super(Crate, self).save(*args, **kwargs)


   
def get_absolute_url(self):
       
return reverse('inventory_crate', kwargs={'crate_slug': self.slug})


   
def clean(self):
       
# ensure that the current number of jars is less than or equal to the capacity
       
if self.jars > self.capacity:
           
raise ValidationError('Capacity of crate exceeded')




class Jar(models.Model):
    product
= models.ForeignKey(Product)
    number
= models.IntegerField()
    slug
= models.SlugField(max_length=13, unique=True, blank=True)
   
# volume in liters
    volume
= models.IntegerField(default=1)
    crate
= models.ForeignKey(Crate)
   
# is_active = not yet sold
    is_active
= models.BooleanField(default=True)
   
# is_available = considered 'in stock'
    is_available
= models.BooleanField(default=True)
    created_at
= models.DateTimeField(auto_now_add=True)
    updated_at
= models.DateTimeField(auto_now=True)


    objects
= models.Manager()
    instock
= InStockJarManager()


   
class Meta:
        ordering
= ['created_at']
        unique_together
= ('product', 'number')


   
@property
   
def name(self):
       
return "%s%d" % (self.product, self.number)


   
def __str__(self):
       
return self.name


   
def save(self, *args, **kwargs):
       
self.slug = ''.join([self.product.slug, str(self.number)])
       
super(Jar, self).save(*args, **kwargs)


   
def get_absolute_url(self):
       
return reverse('inventory_jar', kwargs={'jar_slug': self.slug})


I have written an admin action named "move_multiple_jars" on the JarAdmin class which allows me to select multiple Jar objects and move them to a particular Crate object.  Right now, the intermediate page for that admin action has a form "MoveMultipleJarsForm" with a drop down list which contains all Crate objects sorted by number.  This admin action works in that it does move the Jar objects from one Crate object to another.  I would like that drop down list to contain only the Crate objects which have sufficient extra capacity to hold the number of Jar objects selected.

This code is from admin.py:

class JarAdmin(SmarterModelAdmin):
    valid_lookups
= ('product',)
    form
= JarAdminForm
    list_display
= ('product', 'number', 'is_active', 'is_available', 'crate',)
    list_display_links
= ('number',)
    list_filter
= ('product', 'crate', 'is_active')
    list_per_page
= 50
    ordering
= ['-created_at', 'is_active']
    search_fields
= ['product', 'crate']
    readonly_fields
= ('created_at', 'updated_at',)
    actions
= ['move_multiple_jars']


   
class MoveMultipleJarsForm(forms.Form):
       
# JMT: this needs to somehow be restricted to those crates that have room
        dest
= forms.ModelChoiceField(queryset=Crate.objects.all().order_by('number'))


   
def move_multiple_jars(self, request, queryset):
        form
= None


       
if 'apply' in request.POST:
            form
= self.MoveMultipleJarsForm(request.POST)


           
if form.is_valid():
                dest
= form.cleaned_data['dest']


                count
= 0
               
for jar in queryset:
                    jar
.crate = dest
                    jar
.save()
                    count
+= 1


                plural
= ''
               
if count != 1:
                    plural
= 's'


               
self.message_user(request, "Successfully moved %d jar%s to %s" % (count, plural, dest))
               
return HttpResponseRedirect(request.get_full_path())
       
if not form:
            form
= self.MoveMultipleJarsForm()


       
return render(request, 'admin/move_multiple_jars.djhtml', {
           
'jars': queryset,
           
'move_multiple_jars_form': form,
           
})


    move_multiple_jars
.short_description = "Move multiple jars to new crate"




admin
.site.register(Jar, JarAdmin)


I don't know if it helps, but this is the content of admin/move_multiple_jars.djhtml:

{% extends "admin/base_site.html" %}


{% block content %}


<p>Moving multiple jars from crate {{ crate.name }}:</p>


<form action="" method="post">
   
{% csrf_token %}


   
<p>Move the following jars:</p>


   
<ul>
       
{% for jar in jars %}
       
<li>
           
{{ jar }}
           
<input type="hidden" name="_selected_action" value="{{ jar.pk }}">
       
</li>
        {% endfor %}
    </
ul>


   
{{ move_multiple_jars_form }}


   
<input type="hidden" name="action" value="move_multiple_jars" />
   
<input type="submit" name="apply" value="Move jars" />
</form>


{% endblock %}



Thank you in advance for any help you may be able to give!

Jack.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/c18c7558-55ac-4da2-9fd1-22392090ecf1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/2fb65589-bf03-4a79-a114-f8517807f5b1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment