Multi-level hierarchy dropdown in django? - django

I'm looking to categorize my entries, the catch is there are multiple levels of categories I want. An example:
css
layout
floats
specificity, selectors
html
html 5
In this example, css and html are parent categories, css has 2 children and layout has a child of floats.
I suppose the schema I would need would be
class Category:
name = models.TextField()
parentId = models.IntegerField(blank=True)
What I'm clueless on is, how would I be able to make a multi-level dropdown in my admin so that when I post entries I could select a category easily?
So to reiterate, how would I be able to generate a multi-level nested dropdown menu so that when I enter stuff in my Entry model, I can select one category per entry?

It seems that your problem is slightly different from what you are stating. The issue here is not that much about how to display the hierarchy, which is simple:
def __unicode__(self):
return self.depth * " "
The bummer is how to capture and display the hierarchy / depth. This is a common problem: storing trees in realational databases. As usual, your solution depends on tradeoffs between write / read heavy and how much to normalize. You could, for example, on the 'save' method of the model to recursively get to the root and from there store a 'depth' attribute on the nodes. My suggestion is to use django mptt . It is pretty solid and solves much of the normal hurdles. As a bonus, you get a good api for common tree tasks.

Related

Can you suggest better Django model design? (I'm django beginner)

I'm designing a simple Django model called Clothes. Basically it retrieves what kinds of clothes the user have. I categorized clothes as more than 20 types (such as hoodies, jean, pants) and as big three types: "top", "bottom", "shoes".
In ClothesView, I want to show first 5 clothes for each "top", "bottom", "shoes". And it will retrieve 5 more for individual category if user clicks more (So if user click 'more top', it will return 5 more clothes of 'top' type.
For your better understanding, I wrote the Clothes model conceptually.
class Clothes(models.Model):
id
type = # hoodie, shirts, pants, jean, coat, and so on (more than 20)
big_type = # top, bottom and shoes
owner = ForeignField # some one who post
Expected output (this is just my guess!)
Retrieve 5 clothes for each parent_types ("top", "bottom", "shoes")
user.clothes_set.filter(big_type="top")[:5]
user.clothes_set.filter(big_type="bottom")[:5]
user.clothes_set.filter(big_type="shoes")[:5]
Retrieved 5 more clothes for "top"
user.clothes_set.filter(big_type="top)[5:10]
Retrieve all "hoodies" from my clothes <- this looks ok
user.clothes_set.filter(type="hoodies")
Can you suggest better of efficient model? I may add new Type class and put "through" ... (I'm not sure)
I think you are doing it right. Read here, limiting-queryset.
If you want to query the database in this way. This is the good option instead of retrieving the whole queryset and then slicing it. Through this, the LIMIT for slicing will be issued to the database so, slicing will be done in the database.
On the second opinion, I would advise you to create a new model for Types with the fields type and id.
Then, map it using ManyToMany Relation, if you are not in the case.
For that querying may look like this,
type = Type.objects.get(type="Top").id
clothes = Clothes.objects.filter(type=type)[:5]
Having a different model for Type will help you at the client side rendering.

Haystack scores make no sense

I'm using haystack with elastic search for a project, but the scores I get make no sense (to me).
The model I'm trying to index and search looks similar to:
class Car(models.Model):
name = models.CharField(max_length=255)
class Color(models.Model):
car = models.ForeignKey(Car)
name = models.CharField(max_length=255)
And the search index, even if I'm interested in cars, I want to search them by color as I want to display a pic of that color specifically:
class CarIndex(indexes.SearchIndex, indexes.Indexable):
text = CharField(document=True)
def get_model(self):
return Color
def prepare_text(self, obj):
# Some cleaning
return " ".join([obj.name, obj.car.name])
Now I add a car with three colors, a LaFerrari in Red, Black and White. Having only one model of car, for search purposes there are 3 cars.
So I check Kibana and I get a normal output.
Then I perform a normal search: LaFerrari
All three models have the same info, changing only the color name on the text field. I've even tried removing the color from the text, and guess what I got.
After this fiasco, I tried the python elasticsearch library, and I got normal results (doing manual index and search), all three colors had the same score if I performed a search for LaFerrari.
Any idea what is going on?
I'm thinking about moving from haystack to plain elasticsearch, any recommendations?
If you want to search more distinctively you should add two more fields to the index:
color (and this is really the color like white however you name the models and attributes)
name (the brand name)
The catch-all document field will get you only so far. You would have to make it so that Elasticsearch uses a DisMax query and searches on all configured fields for the given search terms.
https://www.elastic.co/guide/en/elasticsearch/reference/1.7/query-dsl-dis-max-query.html
I've only used the SearchQuerySet+Elastic (based on the catch-all field) so far (and custom+Solr a lot). While the SearchQuerySet fits in very nicely with the Django ORM it will only get you so far. So, you are probably right that you might have to use custom code for querying. I would still recommend Haystack for indexing though (it might be slower but very easy to setup and maintain).
Looking at your example, what you gain with different fields would be:
You search for Laferrari and this is the exact value found in all three documents in the field name (or brand_name). The results will then have the same scores.
Different fields also enable you to use facets: https://www.elastic.co/guide/en/elasticsearch/reference/1.7/search-facets.html#search-facets

Django Multiple Levels Choices for Field

I have a model that I would like to use the choices= option for, but three levels deep.
class Doctor(models.Model):
...
zipcode = models.CharField(max_length=10, choices=AREAS, null=True, blank=True)
Within the "zipcode" dropdown in the admin, I would like the hierarchy to be:
Bronx
--Kingsbridge
----10463
----10471
--Fordham
----10458
----10467
----10468
Brooklyn
--Borough Park
----11204
etc.
Then, if I choose zip code 10463, the Doctor object will be associated with the Kingsbridge area in the Bronx. I'm trying this a variety of different ways. The closest I've come is using this:
AREAS = (
('Bronx', (('Kingsbridge', ('10463', '10463'),),)),
...
)
Unfortunately, that gives me this hierarchy:
Bronx
--('10463', '10463')
which is weird and not helpful. Can anybody see where I'm going wrong? Is this hierarchy possible? Would it be smarter to just create another table in the app called Areas and use a manytomany field? The more I think about it, the more I think I have to use a manytomany field. Thanks in advance
As per https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.Field.choices,
it seems to be only 2 level hierarchy is supported. If you need more than 2 levels, you need to use either custom widgets (or) multiple fields with Foreign Key relationships.

Django admin: Inline of a Many2Many model with 2 foreign keys

after wracking my brain for days, I just hope someone can point me to the right approach.
I have 4 Models: Page, Element, Style and Post.
Here is my simplyfied models.py/admin.py excerpt: http://pastebin.com/uSHrG0p2
In 2 sentences:
A Element references 1 Style and 1 Post (2 FKs).
A Page can reference many Elements, Elements can be referenced by many pages (M2M).
On the admin site for Page instances I included the M2M relation as 'inline'. So that I have multiple rows to select Element-instances.
One row looking like: [My Post A with My Style X][V]
What I want is to replace that one dropdown with 2 dropdowns. One with all instances of Post and one with all instances of Style (creating Element instances in-place). So that one row would look similar to the Element admin site: [My Post A][V] [My Style X][V]
Sounds easy, but I'm just completely lost after reading and experimenting for 2 days with ModelForms, ModelAdmins, Formsets, ... .
Can I do that without custom views/forms within the Django admin functionality?
One of my approaches was to access the Post/Style instances from a PageAdminForm like this, trying to create a form widget manually from it... but failed to do so:
p = Page.objects.get(pk=1)
f = PageAdminForm(instance=p)
f.base_fields['elements'].choices.queryset[0].post
Any advice or hint which way I need to go?
Thank you for your time!
I got exactly what I wanted after removing the M2M field and linking Elements to a Page with a 3rd ForeignKey in Element:
class Element(models.Model):
page = models.ForeignKey(Page)
post = models.ForeignKey(Post)
style = models.ForeignKey(Style)
Actually a non-M2M link makes more sense for my application after all.
Memo to self: Rethink model relations before trying to outsmart Django :-(

Django query to select parent with nonzero children

I have model with a Foreign Key to itself like this:
class Concept(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey('self')
But I can't figure out how I can select all concepts that have nonzero children value. Is this possible with django QuerySet API or I must write custom SQL?
If I understand it correctly, each Concept may have another Concept as parent, and this is set into the category field.
In other words, a Concept with at least a child will be referenced at least once in the category field.
Generally speaking, this is not really easy to get in Django; however if you do not have too many categories, you can think for a query of the like of SELECT * FROM CONCEPTS WHERE CONCEPTS.ID IN (SELECT CATEGORY FROM CONCEPTS); - and this is something you can map easily with Django:
Concept.objects.filter(pk__in=Concept.objects.all().values('category'))
Note that, as stated on Django documentation, this query may have performance issues on certain databases; therefore you should instead put it as a list:
Concept.objects.filter(id__in=list(Concept.objects.all().values('category')))
But please be aware that this could hit some database limitation -- for instance, Oracle allows up to 1000 elements in such lists.
How about something like this:
concepts = Concept.objects.exclude(category=None)
The way you have it written there will require a value for category. Once you have fixed that (with null=True in the field constructor), use this:
Concept.objects.filter(category__isnull=False)