Dynamic URLs for hierarchy tree on Django - django

I would like to build a hierarchy tree of content with no depth limits.
models.py:
class Element(models.Model):
name = models.CharField(verbose_name="Nombre", max_length=250)
parent = models.ForeignKey("self", on_delete=models.CASCADE)
slug = models.TextField(verbose_name="Slug", blank=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Element, self).save(*args, **kwargs)
.../element_n-1/element_n/element_n+1/...
How do I have to write the path in urls.py to get this functionality?

You can use path: type converter for your path() definition:
urlconf.py:
path('prefix/<path:path>', my_view),
More info: https://docs.djangoproject.com/en/3.1/topics/http/urls/#path-converters
views.py:
def my_view(request, path):
elements_list = path.split('/')
...
But as it was said in the previous answer, there's a limit for URL length so your path cannot include arbitrary number of elements.

Such URL is not possible to create in any web framework, because URLs have length limits (2000 characters long), so no web framework will allow a URL to accept growing dynamically based on a Model Hierarchy inside of it.
You need to think of another way to let your app deliver such functionality, in a different way other than URLs.

Related

matching django url with views using a model slug

my problem is that I can not establish a reverse match, most likely doing something wrong with my url definition (?). Ultimately what I am trying to do is the following:
User selects 2 location points which the 'new_pointview' view, saves into a DB. I also define a unique slug which contains location information and save it to the DB via the save() within the model. Then the user should be redirected to a url (pointview view) which uses the slug I created in the previous step i.e /pointview/slug. Here is the code:
models.py
class Points(models.Model):
starting_point_longitude = models.FloatField(null=True)
starting_point_latitude = models.FloatField(null=True)
ending_point_longitude = models.FloatField(null=True)
ending_point_latitude = models.FloatField(null=True)
url = models.SlugField(max_length=250, null=True, unique=True, blank=False)
def save(self, *args, **kwargs):
self.url = 'start_lon-{0}-start_lat-{1}-end_lon-{2}-end_lat-' \
'{3}'.format(self.starting_point_longitude,
self.starting_point_latitude,
self.ending_point_longitude,
self.ending_point_latitude)
super(Points, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('pointview', kwargs={'url': self.url})
views.py
def pointview(request, url):
point = get_object_or_404(Points, url=url)
content = {'starting_point_longitude':
point.starting_point_longitude,
'starting_point_latitude':
point.starting_point_latitude,
'ending_point_longitude':
point.ending_point_longitude,
'ending_point_latitude':
point.ending_point_latitude}
return render(request, 'points.html', {'user_bundle': content})
def new_pointview(request):
Points.objects.create(
starting_point_longitude=request.POST['starting_point_longitude'],
starting_point_latitude=request.POST['starting_point_latitude'],
ending_point_longitude=request.POST['ending_point_longitude'],
ending_point_latitude=request.POST['ending_point_latitude'],
)
points_filtered = Points.objects.filter(
starting_point_longitude=request.POST[
'starting_point_longitude']).filter(
starting_point_latitude=request.POST[
'starting_point_latitude'])
unique_url = points_filtered.values()[0]['url']
return redirect('/pointview/{0}/'.format(unique_url))
urls.py
urlpatterns = [
path(r'^pointview/(?P<url>[-\w]+)/$', views.pointview, name='pointview'),
path('^new_pointview/', views.new_pointview, name='new_pointview'),
]
The error:
The current path, pointview/start_lon-738949.9146592747-start_lat--153698.8751025315-end_lon-759997.8063993475-end_lat--168467.65638300427/, didn't match any of URL patterns. Hope you can give me some feedback here..
For future reference, it was a regex problem, using the following non intuitive regex solved it:
url('^pointview/(?P<url>start_lon[--W]+start_lat[--W]+end_lon[--W]+end_lat[--W]+)/', views.pointview, name='pointview')
I would be very interesting though if someone can give a more elegant solution.

Django: How to create an instance of a model without a form but by a button click

I am new to Django, so pardon me if my question is naive.
I am trying to achieve a use case of text mining application on uploaded files, I am trying to show a "Get Started" button in the landing / home page. On button click I am rendering a file upload option.
Now I am trying to treat this as a Scan Job, and wanting jobID to get created with the click of the button and the same jobID to be displayed further pages and this jobID to be used as primary key in other models.
Below is the model I created.
from django.db import models
class sfs_job_model(models.Model):
sfs_job_id = models.CharField(max_length=250,default="", editable=True)
sfs_job_start_date_time = models.DateTimeField(auto_now_add=True, null=False)
sfs_job_end_date_time = models.DateTimeField(auto_now_add=True,null=False)
sfs_job_status = models.CharField(max_length=250, editable=False,empty_value=None,null=True)
sfs_job_summary = models.CharField(max_length=1000)
def save(self, *args, **kwargs):
if self.job_id is None:
self.job_id = 'SFS-' + str(self.id)
return super(sfs_job_model, self).save(*args, **kwargs)
Please guide me how to achieve the desired use case.
Thanks in advance
I think you sfs_job_id create as UUID and use sfs_job_id usage anywhere are the same and unique.
`def save(self, *args, **kwargs):
if self.sfs_job_id is None:
self.sfs_job_id = 'SFS-' + self.sfs_job_start_date_time.isoformat()
return super(sfs_job_model, self).save(*args, **kwargs)`

change my url from depending on id to title | django

I've a url for checking books by its id:
path('book/<int:book_id>', views.book, name='book'),
view:
def book(request, book_id):
book = get_object_or_404(Book, pk=book_id)
context = {
'book': book
}
return render(request, 'media/book.html', context)
but my client asked me to change it for the title instead but I tried it and it didn't seem to work, there are no examples for it in the docs either.
The NOTE in the answer above does not take SEO into account. For SEO purposes, it is much better to have a url that includes the name of the book rather than just the ID. If you are already in production, then remember to do a permanent redirect in your view from all ID-based urls to the slug-based url. Your Book model definition (or Product or whatever you've called it) should include a slugified field:
class Book(models.Model):
name = models.CharField(...)
slug = models.CharField(max_length=255, default='', unique=True, blank=True)
other_fields...
def save(self, *args, **kwargs):
# Create slug for SEO
#Don't ever change once established since is part of the URI
if self.slug is None or self.slug == '':
self.slug = slugify(self.name)
super(Book, self).save(*args, **kwargs)
You need to change the url to 'book/<str:book_title>', and also adjust your function accordingly:
def book(request, book_title):
book = get_object_or_404(Book, yourfieldfortitle=book_title)
...
It might be helpful to try accessing the url first with a pattern that you know must work. NOTE: this makes the strong assumption that a book is identified by its title, otherwise using the primary key is the proper way even if it doesn't "look nice" as a url.

Django: limit models.ForeignKey results

I have an order model:
class Order(models.Model):
profile = models.ForeignKey(Profile, null=True, blank=True)
Which returns all possible profiles for an order, which is not necessary and slowing down the loading of the admin order page.
I want the profile returned to simply be the profile of the user who placed the order. I've tried changing it to:
class Order(models.Model):
profile = models.ForeignKey(Profile, null=True, blank=True, limit_choices_to={'order': 99999})
which returns the correct profile for order number 99999, but how can I get this dynamically. The Order model is not aware of the 'self', but the order number is contained in the URL.
What is the best way to do this?
If you are using the Django Admin, you can override the method formfield_for_foreignkey on your ModelAdmin class to modify the queryset for the profile field dinamically based on a GET parameter for instance (as you have access to request inside the method.
Look at this example from the docs (adapted for your case):
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "profile":
# You can get the order number from the GET parameter:
# order = request.GET.get('order')
# or a fixed number:
order = '12345'
kwargs["queryset"] = Profile.objects.filter(order=order)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Reference in the docs: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I took another look at this and it seems to be working, although it seems like a bit of hack! The problem was that the order number doesn't seem to exist in the request so I am parsing the URL requested. I put this in my order admin:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "profile":
try:
order = int(filter(str.isdigit, str(request.path_info)))
except:
order = request.GET.get('order')
kwargs["queryset"] = Profile.objects.filter(order=order)
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
The line that filters the url string for an integer works for the change order page of the admin, but didn't work for the order page overview, so I added the try/except. Any suggestions/improvements welcome!
I assume from the context you're referring to the display on the Django Admin page. If you set
raw_id_fields = {'my_foreign_key'}
you will get a number, text description of related model (from str) and a nice popup box to open an instance of the admin page for your related models.
You could alternatively use
list_select_related = True
to get the same behaivour you have now but with a couple of order of magnitudes lower number of queries.

Django filebrowser, model specific directory parameter for FileBrowserField

Using django-filebrowser and the FileBrowserField I am attempting to assign the directory parameter based off a key in the model itself. So using a blog example, if I were saving photos for different posts, the photos would be in directories named after the blog id. So if the MEDIA_ROOT was/some/path/to/media/filebrowser_files/ I wish to dynamically assign the directory paramter to be MEDIA_ROOT+str(model_pk)
At this point I have attempted to do something resembling the following, but do not understand how to obtain the id of the current object. (DoesNotExist exception with "No exception supplied") I know the error exists within the attempt to use self.page, but I do not know how to do this correctly. Could someone provide some insight as to where my logic is flawed and what I can do to fix it? Thanks much.
class PageImage(models.Model):
page = models.ForeignKey(Page)
page_photo = FileBrowseField("Image", max_length=200, blank=True, null=True)
def __init__(self, *args, **kwargs):
super(PageImage, self).__init__(*args, **kwargs)
path = settings.MEDIA_ROOT+'pages/unfiled/'
if self.page:
path = settings.MEDIA_ROOT+'pages/'+str(self.page)+'/'
if not os.path.exists(path):
os.makedirs(path)
self._meta.get_field_by_name("page_photo")[0].directory = path
I realize I didn't look closer at your code. self.page will not work because you're initializing the Model instance and self.page has not been set to a page in the database. You should try instead:
class PageImage(models.Model):
page = models.ForeignKey(Page)
page_photo = FileBrowseField("Image", max_length=200, blank=True, null=True)
def save(self, *args, **kwargs):
path = settings.MEDIA_ROOT+'pages/unfiled/'
if self.page:
path = settings.MEDIA_ROOT+'pages/'+str(self.page)+'/'
if not os.path.exists(path):
os.makedirs(path)
self._meta.get_field_by_name("page_photo")[0].directory = path
super(PageImage, self).save(*args, **kwargs)
doing what you want only after you've assigned a Page to the field in PageImage.