Django paginator not providing content on next page - django

I use Django paginator to break my search results to few pages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
In my view I perform the search based on the output from the form
#login_required
def payment_range_list(request):
#import pdb; pdb.set_trace()
title = 'payment list'
#import pdb; pdb.set_trace()
if request.method == "POST":
form = PaymentRangeForm(request.POST)
#import pdb; pdb.set_trace()
if form.is_valid():
start_date = form.cleaned_data['start_date']
end_date = form.cleaned_data['end_date']
building = form.cleaned_data['building']
payment_list = LeasePaymentFilter(request.GET, queryset=LeasePayment.objects.filter(payment_date__range=[start_date, end_date],lease__unit__building = building ))
paginator = Paginator(payment_list, 25) # Show 25 contacts per page
page = request.GET.get('page')
try:
payment_page = paginator.page(page)
except PageNotAnInteger:
payment_page = paginator.page(1)
except EmptyPage:
payment_page = paginator.page(paginator.num_pages)
else:
payment_list = None
payment_page = None
start_date = None
end_date = None
building = None
else:
payment_list = None
payment_page = None
start_date = None
end_date = None
building = None
#form = PaymentRangeForm()
form = PaymentRangeForm(initial = {'building': 0 })
return render(request,'payment/payment_range.html', {'form':form, 'payment': payment_list,'page_payment':payment_page, 'start_date':start_date, 'end_date':end_date, 'building':building })
On the top of the page I have a form that I use for search and 2nd part is used to display the results.
{% extends "rent_base.html" %}
{% load crispy_forms_tags %}
{% block title %}
Lease List
{% endblock title %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-10 col-sm-offset-1 hid">
<h4 class="display-4">List > Payments> by Date Range </h4><div style="float:right;"> CSV for current range payments</div>
</div>
</div>
<div class="row">
<div class="col-sm-10 col-sm-offset-1 hid">
<form method="POST" class="form" action="" method="get">
{% csrf_token %}
{{ form|crispy}}
<br>
<button type="submit" class="btn btn-primary btn-primary">Search</button>
</form>
<br>
<b>Start date:</b> {{ start_date }} <b>End date:</b>{{ end_date }}
<br>
<table class="table table-striped">
<tr>
<th># Payment</th>
<th>Payment type</th>
<th>Amount </th>
<th>Deposit</th>
<th>Lease</th>
<th>Unit</th>
<th>Building</th>
<th>Term</th>
<th>Payment date</th>
<th>Payment method</th>
</tr>
{% for payment in page_payment %}
<tr>
<td> {{ payment.id }}</td>
<td> {{ payment.get_payment_type_display }}</td>
<td> {{ payment.amount }}</td>
<td>
{% if payment.is_deposit %}
<span class="glyphicon glyphicon-ok text-success" aria-hidden="true" ></span>
{% else %}
<span class="glyphicon glyphicon-remove text-warning" aria-hidden="true" ></span>
{% endif %}
</td>
<td> {{ payment.lease }}</td>
<td> {{ payment.lease.unit.number }}</td>
<td> {{ payment.lease.unit.building.name }}</td>
<td> {{ payment.leaseterm }}</td>
<td> {{ payment.payment_date }}</td>
<td> {{ payment.get_method_display }}</td>
</tr>
{% endfor %}
</table>
<div class="pagination">
<span class="step-links">
{% if page_payment.has_previous %}
previous
{% endif %}
<span class="current">
Page {{ page_payment.number }} of {{ page_payment.paginator.num_pages }}.
</span>
{% if page_payment.has_next %}
next
{% endif %}
</span>
</div>
</div>
</div></div>
{% endblock content %}
My problem is that when my search returns amount of results more than 1 page and I click on next pages. The next pages are always empty.
Any feedback where my problem is?

The problem is that your serch form is POST. So when you click next button you send GET request and if request.method == "POST": part of you view is not triggered.
I suggest you to change serch form to GET:
<form method="GET" action="">
<input type="text" name="q" placeholder="Search" value="{{ request.GET.q }}">
<input type="submit" value="Search">
</form>
And change next button to this:
{% if payment.has_next %}
next
{% endif %}
This will save search value in address bar even after you move to the next page.

Related

HTMX form submission produces a duplicate form

{% extends "IntakeApp/base3.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% block heading %}
<h2>Allergies for {{request.session.report_claimant}}</h2>
{% endblock %}
{% block content %}
<form hx-post="{% url 'allergy' %}" hx-target="#allergy_target" hx-swap="outerHTML">{% csrf_token %}
<div class="form-row">
<div class="form-group col-md-2 mb-0">
{{ form.allergen|as_crispy_field }}
</div>
</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<div class="container-fluid">
<table class="table table-striped table-sm" id="med-table">
<thead>
<tr>
<th>Allergen</th>
</tr>
</thead>
<tbody id="allergy_target">
{% for allergy in allergy_list %}
<tr>
<td>{{allergy.allergen}}</td>
<td>
<form method="POST" action="{% url 'allergy-delete' allergy.id %}">{% csrf_token %}
<input class="btn btn-danger btn-sm" type="submit" value="Delete">
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
class AllergyCreateView(generic.CreateView):
model = Allergy
template_name = 'IntakeApp/allergy_form.html'
form_class = AllergyForm
def form_valid(self, form):
form.instance.assessment = Assessment.objects.get(id=self.request.session['assessment_id'])
return super(AllergyCreateView, self).form_valid(form)
def get_success_url(self):
return reverse_lazy("allergy")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
assessment_id = self.request.session['assessment_id']
allergy_list = Allergy.objects.filter(assessment=assessment_id)
context["allergy_list"] = allergy_list
return context
I tried to all the different hx-swap options, but none fix it...It does post correctly and the swap does work, just not sure why I am getting another form in there. Please help
I added the View above. I think thats were my issue is...not sure if I am supposed to be doing this way or not?
Seems like you're trying to render the same html content in your view, which instead should only take a specific element.
You can try to use hx-select="#allergy_target" so htmx will only fetch the table content.

How to display uploaded pdf file along with machine name and operation number based on select from dropdown

In this project, I want to display machine name and operation number along with uploaded pdf files based on select machine name and operation number from dropdown menu. This project is working but when I add and select another file in same machine name and operation number, it is displaying two pdf files along with previous pdf file of another machine name and operation number, exactly I don't want it. It should display machine name and operation number along with uploaded pdf file based on select from dropdown menu. And also when I upload another pdf files in same machine name and operation number, it should display two pdf files along with same machine name and operation number within same row.
This project is working fine but I want above validations.
Please anyone can help me out, this will be great for me. Please..
views.py:
def upload(request):
controlmachines = Controlmachine.objects.all()
return render(request,'usermaster/upload.html',{'machines':machines})
def save_machine(request):
if request.method == "POST":
machine_name = request.POST.get('machinename', '')
operation_no = request.POST.get('operationname','')
choiced_cmachine = Controlmachine.objects.filter(machine_name=machine_name, operation_no=operation_no)
cmachines = Controlmachine.objects.all()
return render(request,'usermaster/upload.html',{'machines':machines,'choiced_cmachine':choiced_cmachine})
def index(request):
if request.method == 'POST':
form = ControlmachineForm(request.POST, request.FILES)
if form.is_valid():
model_instance = form.save()
model_instance.save()
else:
form = ControlmachineForm()
controlmachiness = Controlmachine.objects.all()
return render(request,'usermaster/upload_file.html',{'form':form,'controlmachiness':controlmachiness})
upload.html:
<form action="{% url 'save_machine' %}" method="post">
{% csrf_token %}
<label for="machinename">Select Machine Name:</label>
<select name="machinename" id="machinename">
{% for machine in cmachines %}
<option value="{{ machine.machine_name }}">{{ machine.machine_name }}</option>
{% endfor %}
</select>
<br>
<br>
<label for="operationname">Select Operation Number:</label>
<select id="operationname" name="operationname">
{% for machine in cmachines %}
<option value="{{ machine.operation_no }}">{{ machine.operation_no }}</option>
{% endfor %}
</select>
<br>
<br>
<br>
<input type="submit" value="Save">
</form>
<tr>
{% for choice in choiced_cmachine %}
<td>{{choice.machine_name}}</td>
<td>{{choice.operation_no}}</td>
<td>
{% for file in controlmachines %}
view file
{% endfor %}
</td>
{% endfor %}
</tr>
control_uploadfile.html:
<html>
<head>
<title>Master File Upload</title>
</head>
<body>
<p><h1>Control File Upload</h1></p>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Save</button> <br><br>
</form>
</body>
</html>
control_show.html:
{% extends "master/control_base.html" %}
{% block title %}Control File{% endblock title %}
{% block content %}
<div class="col-md-12">
<div class="table-responsive">
<table id="bootstrapdatatable" class="table table-striped table-bordered" width="90%">
<thead>
<th><input type="checkbox" id="checkall" /></th>
<th>ID</th>
<th>Machine Name</th>
<th>Operation Number</th>
<th>File</th>
<th>Delete</th>
</thead>
<tbody>
{% for control in controlmachines %}
<tr>
<td><input type="checkbox" class="checkthis" /></td>
<td>{{ control.id }}</td>
<td>{{ control.machine_name }}</td>
<td>{{ control.operation_no }}</td>
<td>{{ control.control_uploadfile }}</td>
<td><p data-placement="top" data-toggle="tooltip" title="Delete"></span></p></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock content %}
models.py:
class Controlmachine(models.Model):
machine_name = models.CharField(max_length=100)
operation_no = models.IntegerField()
control_uploadfile = models.FileField(upload_to='documents/')
class Meta:
db_table = "controlmachine"
forms.py:
class ControlForm(forms.ModelForm):
class Meta:
model = Controlmachine
fields = ['machine_name', 'operation_no', 'control_uploadfile'] #https://docs.djangoproject.com/en/3.0/ref/forms/widgets/
widgets = { 'machine_name': forms.TextInput(attrs={ 'class': 'form-control' }),
'operation_no': forms.TextInput(attrs={ 'class': 'form-control' }),
'control_uploadfile': forms.ClearableFileInput(attrs={ 'class': 'form-control' }),
}
Do this: Here I have added if condition that is used to connect machine name to uploaded file.
<form action="{% url 'save_machine' %}" method="post">
{% csrf_token %}
<label for="machinename">Select Machine Name:</label>
<select name="machinename" id="machinename">
{% for machine in cmachines %}
<option value="{{ machine.machine_name }}">{{ machine.machine_name }}</option>
{% endfor %}
</select>
<br>
<br>
<label for="operationname">Select Operation Number:</label>
<select id="operationname" name="operationname">
{% for machine in cmachines %}
<option value="{{ machine.operation_no }}">{{ machine.operation_no }}</option>
{% endfor %}
</select>
<br>
<br>
<br>
<input type="submit" value="Save">
</form>
<tr>
{% for choice in choiced_cmachine %}
<td>{{choice.machine_name}}</td>
<td>{{choice.operation_no}}</td>
<td>
{% for file in controlmachines %}
{% if file == choice and file == choice %}
view file
{% endif %}
{% endfor %}
</td>
{% endfor %}
</tr>

form doesn't submit productbacklogs to database

here is my project code .
I after posting data by form nothing happens.
#model.py
from django.db import models
from projectapp.models import Project
class Productbacklog(models.Model):
project=models.ForeignKey(Project,on_delete=models.CASCADE,default=None)
pbId=models.IntegerField(primary_key=True)
pbTitle=models.CharField(max_length=100)
pbPriority=models.IntegerField(blank=True, null=True)
class Meta:
unique_together=('project','pbId')
def __str__(self):
return self.pbTitle
#forms.py
from django import forms
from productbacklogapp.models import Productbacklog
from projectapp.models import Project
class ProductbacklogForm(forms.ModelForm):
class Meta:
model = Productbacklog
exclude=('pbId','project')
fields=['pbTitle']
#views.py
def productbacklogall(request):
if request.method == 'POST':
form = ProductbacklogForm(request.POST)
if form.is_valid():
form.instance.manage = Project.objects.get_or_create(cname=form.cleaned_data['manage_id'])
form.save()
messages.success(request, ('new productbacklog added'))
return redirect('productbacklogall')
else:
pb_all=Productbacklog.objects.all()
return render(request, 'productbacklogall.html', {'pb_all':pb_all})
I think that issue is on forms.py or views.py but I can't find it.
I'm so greatful if anyone can help me.
here is also my html code,when I submit something the method is post but I don't know why it doesn't go to data basse.
#productbacklogall.html
{%extends 'base.html'%}
{%block title%}
<title>backlog all</title>
{%endblock title%}
{%block content%}
</br>
{% if messages %}
{% for message in messages %}
<div class="alert alert-primary" role="alert">
{{ message }}
×
</div>
{% endfor %}
{% endif %}
<div class="container">
<form method="POST" class="row">
{% csrf_token %}
<label class="col-lg-4"></label>
<input type="text" class="form-control" name="Project" placeholder="project title?"/>
<input type="number" class="form-control" name="pbId" placeholder="backlog id?"/>
<input type="text" class="form-control" name="pbTitle" placeholder="pb title?"/>
<button type="submit" class="btn btn-primary col-lg-2">add project</button>
</form>
</div>
</br>
</br>
<table class="table table-bordered text-center">
<thead class="thead-dark">
<tr>
<th scope="col"> backlog-title</th>
<th scope="col">Edit</th>
<th scope="col">delivarable_task</th>
<th scope="col">related project</th>
</tr>
</thead>
<tbody>
{% load static %}
{% if pb_all %}
{% for obj in pb_all %}
<tr>
<td>{{ obj.pbTitle }}</td>
<td><a href='{% url "delete_productbacklog" obj.pbId %}'>delete</a></td>
<td> <a href=#> delivarabletask </a></td>
<td>{{ obj.project.pbTitle }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
{%endblock content%}
Your form data are not valid, to see them, add this code to your views :
else:
messages.error(request, "Error")
like this :
def productbacklogall(request):
if request.method == 'POST':
form = ProductbacklogForm(request.POST)
if form.is_valid():
form.instance.manage = Project.objects.get_or_create(cname=form.cleaned_data['manage_id'])
form.save()
messages.success(request, ('new productbacklog added'))
else:
messages.error(request, "Error")
return redirect('productbacklogall')
else:
pb_all=Productbacklog.objects.all()
return render(request, 'productbacklogall.html', {'pb_all':pb_all})

Unable to edit/delete existing formets

I am new here. I have been working on this issue for the past couple of days. Could someone help me, please?
Problem: Unable to edit/delete existing formsets
I have 1 form and 2 formsets in a view. I can create all 3, successfully.
Template rendering 1 form and 2 formsets
Also, I can edit/delete the form, but can't with the 2 formsets.
models.py
class Component(models.Model):
...
class ComponentImage(models.Model):
component = models.ForeignKey(Component, on_delete=models.CASCADE, related_name='componentimage')
image = models.ImageField(upload_to='component_pics')
caption = models.CharField(max_length=100, null=True, blank=True)
class ComponentWeblink(models.Model):
component = models.ForeignKey(Component, on_delete=models.CASCADE, related_name='componentweblink')
url = models.URLField()
caption = models.CharField(max_length=100, null=True, blank=True)
views.py
def component_create(request):
template = 'component/component_add.html'
if request.method == 'GET':
form = ComponentForm()
images = ImageFormset(queryset=ComponentImage.objects.none(), prefix='images')
links = WeblinkFormset(queryset=ComponentWeblink.objects.none(), prefix='links')
elif request.method == 'POST':
form = ComponentForm(request.POST)
images = ImageFormset(request.POST, request.FILES, prefix='images')
links = WeblinkFormset(request.POST, prefix='links')
if form.is_valid() and images.is_valid() and links.is_valid():
component = form.save(commit=False)
component.updated_by = request.user
component.save()
for form in images:
if form.has_changed():
componentimage = form.save(commit=False)
componentimage.component = component
componentimage.updated_by = component.updated_by
try:
componentimage.save()
except ValueError:
pass
for form in links:
if form.has_changed():
componentweblink = form.save(commit=False)
componentweblink.component = component
componentweblink.updated_by = component.updated_by
componentweblink.save()
messages.success(request, 'Component successfully created.')
return HttpResponseRedirect(reverse_lazy('component:component_detail', args=(component.pk,)))
return render(request, template, {
'form': form,
'images': images,
'links': links,
})
def component_update(request, pk):
obj = get_object_or_404(Component, id=pk)
template = 'component/component_update.html'
if request.method == 'GET':
form = ComponentForm(instance=obj)
images = ImageFormset(instance=obj, prefix='images')
links = WeblinkFormset(instance=obj, prefix='links')
elif request.method == 'POST':
form = ComponentForm(request.POST or None, instance=obj)
images = ImageFormset(request.POST or None, request.FILES or None, instance=obj, prefix='images')
links = WeblinkFormset(request.POST, instance=obj, prefix='links')
if form.is_valid() and images.is_valid() and links.is_valid():
component = form.save(commit=False)
component.updated_by = request.user
component.save()
for form in images:
if form.has_changed():
componentimage = form.save(commit=False)
componentimage.component = component
componentimage.updated_by = component.updated_by
try:
componentimage.save()
except ValueError:
pass
for form in links:
if form.has_changed():
componentweblink = form.save(commit=False)
componentweblink.component = component
componentweblink.updated_by = component.updated_by
componentweblink.save()
messages.warning(request, 'Component successfully updated.')
return HttpResponseRedirect(reverse_lazy('component:component_detail', args=(component.pk, ))) #trailing comma is required for single-item tuples to disambiguate defining a tuple or an expression surrounded by parentheses
return render(request, template, {
'form': form,
'images': images,
'links': links,
})
template.html (for update view)
{% block content %}
<script type="text/javascript" src="{% static 'js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$(function() {
$('#images tbody tr').formset({
prefix: '{{ images.prefix }}',
formCssClass: 'dynamic-images'
});
$('#links tbody tr').formset({
prefix: '{{ links.prefix }}',
formCssClass: 'dynamic-links'
});
})
</script>
<div class="row justify-content-center">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend style="text-align:center" class="border-bottom mb-4">
Update Component
</legend>
<h5 style="text-align:center" >Data</h5>
{% include 'snippets/form.html' %}
<hr>
<h5 style="text-align:center" >Images</h5>
<table class="col col-md-12" id="images">
<tbody>
{{ images.management_form }}
{{ images.non_form_errors }}
{% for form in images.forms %}
<tr>
<td class="px-0 py-0 form-control{% if form.image.errors %} is-invalid{% else %} border-0{% endif %}">{{ form.image }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.image.non_field_errors }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.image.errors }}</td>
<td class="px-0 py-0 form-control{% if form.image.errors %} is-valid{% else %} border-0{% endif %}">{{ form.caption }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.caption.non_field_errors }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.caption.errors }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<hr>
<h5 style="text-align:center" >Weblinks</h5>
<table class="col col-md-12" id="links" border="0" cellpadding="0">
<tbody>
{{ links.management_form }}
{{ links.non_form_errors }}
{% for form in links.forms %}
<tr>
<td class="px-0 py-0 form-control{% if form.url.errors %} is-invalid{% else %} border-0{% endif %}">{{ form.url }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.url.errors }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.url.non_field_errors }}</td>
<td class="px-0 py-0 form-control{% if form.url.errors %} is-valid{% else %} border-0{% endif %}">{{ form.caption }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.caption.errors }}</td>
<td class="border-0 px-0 py-0 form-control" style="color:red">{{ form.caption.non_field_errors }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<button class="btn btn-primary" type="submit">
Update
</button>
<a class="btn btn-outline-secondary" href="{% if request.GET.next %}{{ request.GET.next }}{% else %}{% url 'component:components_list' %}{% endif %}">
Cancel
</a>
</fieldset>
</form>
</div>
{% endblock content %}
Please help. Thank you
Used VSCode debugger, as per a friend's suggestion. The problem was with not adding id (hidden_fields).
I added this in the update template:
{% for form in images.forms %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for form in links.forms %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
Also added the image.has_changed() to views.py to avoid accepting empty image fields.
for image in images:
if image.is_valid() and image.has_changed():
try:
image.save()
except ValueError:
pass

Django post duplicates on page refresh

I have this view which adds replies to a topic:
#login_required
def post_reply(request, topic_id):
tform = PostForm()
topic = Topic.objects.get(pk=topic_id)
args = {}
if request.method == 'POST':
post = PostForm(request.POST)
if post.is_valid():
p = post.save(commit = False)
p.topic = topic
p.title = post.cleaned_data['title']
p.body = post.cleaned_data['body']
p.creator = request.user
p.user_ip = request.META['REMOTE_ADDR']
p.save()
tid = int(topic_id)
args['topic_id'] = tid
args['topic'] = topic
args['posts'] = Post.objects.filter(topic_id= topic_id).order_by('created')
return render_to_response("myforum/topic.html",args)
else:
args.update(csrf(request))
args['form'] = tform
args['topic'] = topic
return render_to_response('myforum/reply.html', args,
context_instance=RequestContext(request))
The problem is that when user refershes the page after posting a reply her reply is being duplicated. How to avoid this?
UPDATE:Here is the related template:
{% extends "base.html"%}
{% load static %}
{% block content%}
<div class="panel">
<div class="container">
<!-- Posts -->
<div class="col-md-12">
<h3>{{ topic.title }}</h3>
<table class="table table-striped">
<tr class="col-md-9"><td>{{ topic.description }}</td></tr>
<tr class="col-md-3"><div class="userpanel"><td>{{ topic.created }}</td></div></tr>
{% for post in posts %}
<tr class="col-md-12 userpanel"><td>{{ post.title }}</td></tr>
<tr class="">
<td>
<div class="col-md-9 userpanel">{{ post.body }} <br> {{ post.created }} </div>
</td>
<td>
<div class="col-md-3 userpanel">{{ post.creator }} <br> {{ post.created }} </div>
</td>
</tr>
{% endfor %}
</table>
<hr />
<!-- Next/Prev page links -->
{% if posts.object_list and posts.paginator.num_pages > 1 %}
<div class="pagination">
<span class="step-links">
{% if posts.has_previous %}
previous <<
{% endif %}
<span class="current">
Page {{ posts.number }} of {{ topics.paginator.num_pages }}
</span>
{% if posts.has_next %}
>> next
{% endif %}
</span>
</div>
{% endif %}
<a class="button" href="/forum/reply/{{topic.id}}"> Reply </a>
</div> <!-- End col-md-12 -->
</div> <!-- End container -->
</div>
{% endblock %}
Always - I repeat, always, redirect after a POST.
So instead of doing a return render_to_response("myforum/topic.html",args) when your form is valid, do a return HttpResponseRedirect(url_of_your_view) (https://docs.djangoproject.com/en/1.7/ref/request-response/#django.http.HttpResponseRedirect)
Update after OP's comments:
You should use reverse (https://docs.djangoproject.com/en/1.7/ref/urlresolvers/#reverse) to create the url to redirect to:
return HttpResponseRedirect(reverse('myforum.views.topic', args=[topic_id]))
Also, I recommend to name your views (and drop strings for defining them since they are deprecated), so change your urls.py line like this:
from myforum.views import topic
...
url(r'^topic/(\d+)/$', topic, name='view_topic'),
...
And then just do a reverse('view_topic', args=[topic_id]) to get the url of your view.