a cluster of problems. I'm using the SVN version.
Sorry for the length of this, I'm just trying to explain as fully as I
can what I'm trying to do, what I have and what's going wrong.
The relevant parts of my model, in the simplest terms, look like this.
from django.db import models
class Address(models.Model):
customer = models.ForeignKey("Customer")
street = models.CharField(max_length=128)
city = models.CharField(max_length=128)
def __unicode__(self):
return "%s, %s" % (self.street, self.city)
class Customer(models.Model):
last_name = models.CharField(blank=True, max_length=64)
first_name = models.CharField(blank=True, max_length=64)
billing_address = models.ForeignKey("Address", related_name="+")
def __unicode__(self):
return "%s %s" % (self.first_name, self.last_name)
So customers can have many addresses, and one of those addresses is
pointed to by the customer as being the billing address.
I then have the Customer admin page set up so that Address entries are
edited inline on the same form.
1. The billing address should be required, but obviously when it's a
new Customer there won't be any addresses on file, so there will be
no choices on the billing address dropdown.
So I need a way to accept a blank selection for billing address,
maybe have it labelled as "use first address given below", and then
just complain if no addresses are given below.
Later when there needs to be something to stop the billing address
from being deleted.
2. Related to the previous, there needs to be a constraint so there
must be at least one Address for each customer.
3. When editing an existing customer, only that customer's addresses
should be shown in the dropdown for billing address.
Here's what I've tried...
I set the billing address field to be optional for now.
Problem 3 seemed easiest so I decided to tackle that first, and made a
bunch of customers and addresses so I could test with the database
somewhat populated.
I found the ForeignKey.limit_choices_to in the documentation but since
there's no "self" when I'm setting up the database fields I've no idea
how I'd tell it to limit the options to something like
self.addresses_set.all(), let alone have that updated as addresses are
added, removed, edited in the inline form below.
I first posted this problem on Stacko Overflow
(http://stackoverflow.com/questions/8637912/) and a suggestion was to
use a ModelChoiceField. I had a look at the documentation and it
wasn't obvious what the difference is between that an ForeignKey, plus
it looks like I'd have exactly the same problem as above.
So I'm totally stuck on that one.
Next I had a go at the other two problems. It seemed to me (bearing in
mind I'm a newbie here) it'd be best to add that logic to the
Customer.clean() method -- I could check the number of addresses which
had been entered there and raise an Exception if it was zero. At the
same time I could set the billing address, if not already set, to the
first address given. All sounds simple enough, but apparently not.
In the Customer.clean() method, I can't seem to get at what was posted
as addresses. In there, self.address_set.all().count() is zero. I
don't really see why -- I can get at the other data which was posted
to the form as an object, why not the child objects which are being
posted too?
Perhaps just too early. Following a suggestion in the same Stack
Overflow thread mentioned above, I figured out how to set up a
listeners for the pre_save and post_save signals and inspected the
count of addresses at those points. It's still zero in both cases.
Even after the save. That was very confusing but from what I've found
while Googling it's something to do with the database transaction
having not been finished yet. It seems counter-intuitive. Ideally I'd
like to get at the Address objects before they're committed to the
database so that I can roll back if necessary (in the case that there
are no addresses), but after they're committed would do if there was
no other way -- I could change the Customer object as necessary and
re-save it or delete it. Not sure how I'd let the user know in that
case.
But no -- empty address_set.all() at post_save time. I found a monkey
patch to add a signal for post_transaction
(https://gist.github.com/247844) and with a small tweak (possibly my
Python or my Django is too old or new or something) it worked. I set
up the listener and now I can get at the Address objects from the
Customer object and edit the Customer if necessary (adding the first
address as the billing address). But at this point it's too late to
throw pretty exceptions if something goes wrong, which is a shame.
There's another problem there too. Once the billing address is set to
one of the addresses and then the customer's addresses are later
edited again I get horrific errors saying that the billing address is
set to an unacceptable option. I think what's happening is that the
addresses are being deleted and recreated, and so the reference in the
billing address field now points to a non-existent primary key in the
addresses table.
Since the above doesn't let me warn the user if they haven't entered
any addresses, I needed another approach to let me do that. What I
came up with was setting up a custom ModelForm for the Customer
object's admin interface, and checking in the clean() method there.
The best I could think to do was to print out dir() of various things
to find likely looking methods and to see what data I had. The only
reference to the addresses being entered I could find was in the
self.data dict there. And it's messy, but I got a solution
half-working by looking at self.data["address_set-TOTAL_FORMS"] and
checking the number. But that doesn't cover all possibilities -- it
might not exist (easy to deal with) and, slightly harder, some of the
forms sent might have the "delete" box checked. So I had to count up
to the number of forms and look for "address_set-%n-DELETE" keys. It's
a mess and there's still a case it doesn't catch -- when a form is
sent with the default (untouched) values and so doesn't actually end
up creating an address. I'd see it as being an address and let it
through, but then the billing address wouldn't have anything to point
to. I didn't try to write code to handle that case because it's just
getting way too messy.
So this solution is not working for me.
In fact, looking back, none of my solutions for any of the problems
are adequate. I'm stuck.
Please help! Any suggestions are appreciated. Hopefully there are just
some really easy things I've missed in the documents which will solve
everything, but if the solution is a bit more in depth, so be it.
--bart nagel
--
You received this message because you are subscribed to the Google Groups "Django users" group.
To post to this group, send email to django-users@googlegroups.com.
To unsubscribe from this group, send email to django-users+unsubscribe@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/django-users?hl=en.
No comments:
Post a Comment