Writing a view to fetch user data and add it to the database - django

I make a project which generates a random book when the user press a button. The view that does that is:
def random_book(request):
cartile=Books.objects.all()
random_item=random.choice(cartile)
return render(request, 'carti/book.html', context={"random_item": random_item})
On the page were you are redirected to see the generated random book I want to add a button that says "add to read later".
I know how to make a manyotmany relationship between profile model and books model, but I have no idea how to write a view that gets the generated random book, adds it to the profile view_later field in the database, delete it with another button, what html tag and what should I write inside for "add to read later" and the delete button.
Some help would be appreciated!

I guess that depends on the flow of you website.
If I interpret your idea correctly, one simple way would be to have a view that takes the id of the selected book and add it to read later collection (in the case of the add).
Doing a lot of suppositions, something like this should do the basic,
urlpatterns = [
...
path('site/read_later_add<int:book_pk>/', views.read_later_add),
]
# View (in site/views.py)
def read_later_add(request, book_pk=1):
# get user profile
profile = Profile.objects.get(user=request.user)
# get the book
book = Book.objects.get(pk=book_pk)
# add book to view_later collection
profile.view_later.add(book) # <- this is it
# give some success message
return render(request, 'read_later_add_success.html')
The remove case would be similar just using profile.view_later.remove(book).
BTW, I did not add any error handling or checks, you should definitely should dot that.

If i understand your problem correctly. You can get the random book by implementing a function that return a random number between 1 and the size of Books column. And pass the return integer of this random function to Books.objects.get(pk=random_int)

Related

In Django, how can I calculate or update certain model fields BEFORE any validation happens?

So I'm new to Django...
First some background on how we do things now. We have a custom php system but I am constructing an improved inventory management system in django using only the admin interface. We store part numbers, and it is essential that we do not store duplicates. Part numbers can sometimes be entered with hypens, periods, spaces, etc. We need to be sure that duplicate parts are not added no matter what kind of formatting is entered. With our existing non-django system, we use a regex to strip anything from the string that is not a-zA-Z0-9. The actual entered part number is persisted, and the cleaned number is persisted to the db as well. Then when someone is adding a new part or even searching for a part, this cleaned version of the part number helps to avoid this ambiguity. We do the same for the manufacturer name.
My way of emulating this in django was to add the part_number_clean field along with the part_number field to the model. Then I overrode the save method to calculate the clean part number like so (manufacturer as well):
def save(self, *args, **kwargs):
self.manufacturer_clean = re.sub(r'[^a-zA-Z0-9]', '', self.manufacturer).lower()
self.part_number_clean = re.sub(r'[^a-zA-Z0-9]', '', self.part_number).lower()
super(CatalogProduct, self).save(*args, **kwargs)
The problem is, I need to unique on a combination of part number and manufacturer:
class Meta:
unique_together = ('part_number_clean ', 'manufacturer_clean ')
When I try to save a duplicate record, I get a database integrity violation. So it seems like django is evaluating the unique fields before the save function is called (which makes sense). I just need to know how or which method I should override to calculate these fields BEFORE any validation.
Additionally, I am interested in adding a third field to the unique_together mix that may or may not be filled out. If it is not filled it will just have an empty default value. I hope this will not cause any issues.
It would also be great if when the user tabbed-out of the manufacturer and part number fields, and both were not empty, some js would see if that product exists already, and offer the user the option to click a button and be whisked away to that record, before they waste their time filling out the rest of the data only to find that it already exists. I'm guessing this lies way outside the realm of the admin interface without serious hacking. Is there any way to somehow integrate this with the admin interface? Its working great for me up till now...
I figured it out. I'm posting the answer for anyone else that is curious. This was actually very simple in the end to implement in the model. All one needs to do is implement (override?) the clean() method of the model. In the method, I calculate and set my special fields, then be sure to call self.validate_unique() after. Works like a charm! No need to raise any exceptions, the form will display the error on top perfectly. Doing this in the save method will not work, as the exception cannot be thrown by your code or django at that point. Here is the code:
class CatalogProduct(models.Model):
manufacturer = models.CharField(max_length=100)
manufacturer_clean = models.CharField('Manufacturer',max_length=100,blank=True,editable=False)
part_number = models.CharField(max_length=100)
part_number_clean = models.CharField('Part number',max_length=100,blank=True,editable=False)
def clean(self):
# Calculate manufacturer_clean and part_number_clean
self.manufacturer_clean = re.sub(r'[^a-zA-Z0-9]', '', self.manufacturer).lower()
self.part_number_clean = re.sub(r'[^a-zA-Z0-9]', '', self.part_number).lower()
self.validate_unique()
The model is only responsible for describing data and how that data should be represented between your Python and database environment. It's because of this atomic role that models don't care about validation and what you've just went in there and introduced it.
You need a model form. It can clean the manufacturer and part number and also ensure that uniqueness constraints are satisfied as part of the validation process.

Sharing data between two views through template in Django?

I may have overcomplicated things.
I have two views. The first view generates a bunch of temporary data based on the user's input from the form. Each of the generated data contains a name and misc data. I want to pass only the names to the template to be rendered as a list of hyperlinks. If the user clicks on one of them, the second view should be given the specific name the user clicked on so that the view can manipulate it. The only problem is, I don't know how to get the misc data associated with the name.
The misc data generated could contain random characters that's not a standard character in URLs, so I can't turn misc into a hyperlink like I can with just the name.
I have something like this:
views:
# Displays the temp data names
def display(request):
return render_to_response('display.html',{},context_instance=RequestContext(request))
# User provides input, generate temp data to be displayed as hyperlinks
def search(request):
form = SearchForm(request.POST)
if form.is_valid():
usr_input = form.cleaned_data['input']
data = generate_data(usr_input) # generates a list of (name, misc) data.
request.session['hyperlinks'] = get_list_names(data) # returns only names in data
return HttpResponseRedirect('views.display')
else:
....
# User has clicked on a hyperlink, we must process specific data given its name.
def process_data(request, name):
# How to get associated misc data created from search()?
I haven't written the template yet, but the idea is:
template:
{% for name_link in request.session.hyperlinks %}
<a href={% url process name_link %}>
{% endfor %}
One solution could be creating a bunch of session variables:
for name in get_list_names(data):
request.session[name] = // associated misc data
But this seems like a waste. Plus I'd have to manage deleting the session variable later on since this is only temporary data generated based on user input. A new input from the user would create another huge horde of session variables!
Another solution could be to store it temporarily in the database, but that also seems like a bad idea.
EDIT - Trying out suggestion by christophe31:
I'm not quite sure if I understand your suggestion, but is it something like this?
data_dict = {name1:misc1, name2:misc2, etc...}
encoded = urllib.urlencode(data_dict) # encoded = 'name1=misc1&name2:misc2...etc'
request.session['hyperlinks'] = encoded
A few questions on this though:
1) Wouldn't encoding it using urllib defeat the purpose of having a dictionary? It returns a string rather than a dictionary
2) To expand on (1), what if the misc data had '&' and '=' in it? It would screw up parsing which is the key and value by the second view. Also, misc data may have unusual characters, so allowing that to be part of the url to be displayed may be bad.
3) Does Django protect from allowing the user to maliciously modify the session misc data so that the misc data generated from the first view may be different than the one passed to the second view? That would be a problem!
You may want to put a dictionary as a session variable, set a cookie, or pass as get argument throught the link your data.
For me you have to put all these data in a dictionary before export it as get parameters (with urllib2) or store it in your user's session.
Ask me if you want more info on a suggested way.
Edit:
They are 2 ways I see, by session:
data_dict = {name1:misc1, name2:misc2, etc...}
request.session['hyperlinks'] = data_dict
Or passing to the template the data if no session backend:
data_dict = {name1:misc1, name2:misc2, etc...}
encoded = urllib.urlencode(data_dict)
return render(request, "my_template.html", {"url_params":encoded,}
and
Go to results

Django Processing Form to other Form

I have a view that is an input form that people input their information in (name, address, that sort of thing). They will then click an "ok" button.
After people click "ok" I want them to be redirected to a page that has a table with their inputted information.
Any ideas on how to do this?
So far, I have the first view. When clicking OK all the information is stored in a database. I just don't know how to use it from there.
Note: ModelForms are used
If you look carefully the documentation : https://docs.djangoproject.com/en/dev/topics/forms/#processing-the-data-from-a-form
You could understand that, once you have treated (is_valid() - cleaned_data['']), you have to do a call to
render_to_response('template',{ 'name':name, 'last_name':last_name..}, context_instance=RequestContext(request))
And, in the template called, just make the data you need be shown, as usual..
you could try to pass your newly created object to the next view (which shows the new object) like this, the called view just has to accept your object as parameter.
# ... your form processing here
if form.is_valid():
# create and save your object code here
your_object.save()
return redirect('show_new_data_view', your_object=your_object)
Hope this helps.

django admin actions on all the filtered objects

Admin actions can act on the selected objects in the list page.
Is it possible to act on all the filtered objects?
For example if the admin search for Product names that start with "T-shirt" which results with 400 products and want to increase the price of all of them by 10%.
If the admin can only modify a single page of result at a time it will take a lot of effort.
Thanks
The custom actions are supposed to be used on a group of selected objects, so I don't think there is a standard way of doing what you want.
But I think I have a hack that might work for you... (meaning: use at your own risk and it is untested)
In your action function the request.GET will contain the q parameter used in the admin search. So if you type "T-Shirt" in the search, you should see request.GET look something like:
<QueryDict: {u'q': [u'T-Shirt']}>
You could completely disregard the querystring parameter that your custom action function receives and build your own queryset based on that request.GET's q parameter. Something like:
def increase_price_10_percent(modeladmin, request, queryset):
if request.GET['q'] is None:
# Add some error handling
queryset=Product.objects.filter(name__contains=request.GET['q'])
# Your code to increase price in 10%
increase_price_10_percent.short_description = "Increases price 10% for all products in the search result"
I would make sure to forbid any requests where q is empty. And where you read name__contains you should be mimicking whatever filter you created for the admin of your product object (so, if the search is only looking at the name field, name__contains might suffice; if it looks at the name and description, you would have a more complex filter here in the action function too).
I would also, maybe, add an intermediate page stating what models will be affected and have the user click on "I really know what I'm doing" confirmation button. Look at the code for django.contrib.admin.actions for an example of how to list what objects are being deleted. It should point you in the right direction.
NOTE: the users would still have to select something in the admin page, otherwise the action function would never get called.
This is a more generic solution, is not fully tested(and its pretty naive), so it might break with strange filters. For me works with date filters, foreign key filters, boolean filters.
def publish(modeladmin,request,queryset):
kwargs = {}
for filter,arg in request.GET.items():
kwargs.update({filter:arg})
queryset = queryset.filter(**kwargs)
queryset.update(published=True)

Django ModelForm Validate custom Autocomplete for M2M, instead of ugly Multi-Select

Given the following models (cut down for understanding):
class Venue(models.Model):
name = models.CharField(unique=True)
class Band(models.Model):
name = models.CharField(unique=True)
class Event(models.Model):
name = models.CharField(max_length=50, unique=True)
bands = models.ManyToManyField(Band)
venue = models.ForeignKey(Venue)
start = models.DateField()
end = models.DateField()
The admin area works great for what I'm doing, but I'd like to open the site up a bit so that certain users can add new Events. For the public portions, I have several "administrative" fields on these models that I don't want the public to see (which is easy enough to fix).
My specific problem, though, is changing the display of the ManyToMany selections when creating a new Event. Because the number of Bands possible to list for an event should not be sent along as a multiselect box, I'd like to use an AutoComplete that handles multiples (like the Tags box, here on StackOverflow!).
I have this part working, and it correctly fills in a hidden input with the Band.id's separated by commas for a value. However, I can't understand how to put together letting Django do the validation using the ModelForms, and somehow also validating the 'Bands' selection.
Ideally, I want to auto-complete like the tags here on StackOverflow, and send along the selected Bands ID's in some kind of Delimited string - all while letting Django validate that the bands passed exist, etc, as if I left the annoying multi-select list in place.
Do I have to create my own Auto-Complete Field type for a form or model, and use that? Is there something else I'm overlooking?
I have seen some existing AutoComplete widgets, but I'd really-really-really like to use my own Autocomplete code, since it's already set up, and some of them look a bit convoluted.
There was a lot more text/explanation here, but I cut back because I'm avoiding Wall Of Text. If I left important stuff out, let me know.
It's a little hard to say without knowing exactly what your autocomplete code is doing, but as long as it is sending the ids of the bands like they would be sent with the <select>, the ModelForm should validate them as usual.
Basically, your POST string should look like:
name=FooBar2009&bands=1&bands=3&bands=4&venue=7&start=...
The easiest way to do this might be to use Javascript to add (and remove) a hidden input field for each band entered with the name band and the id of the band as the value. Then, when the user submits the form, the browser will take care of posting the right stuff, and the ModelForm will validate it.
Using the annointed jquery autocomplete plugin,
On the client-side I have something like this:
jQuery("#id_tags").autocomplete('/tagging_utils/autocomplete/tasks/task/', {
max: 10,
highlight: false,
multiple: true,
multipleSeparator: " ",
scroll: true,
scrollHeight: 300,
matchContains: true,
autoFill: true,
});
So, I have a view that returns when I type in a:
http://skyl.org/tagging_utils/autocomplete/tasks/task/?q=a&limit=10&timestamp=1259652876009
You can see the view that serves that here:
http://github.com/skyl/skyl.org/blob/master/apps/tagging_utils/views.py
Now, it's going to be a little tricky .. you might except the POST, then in the clean method of the field try to .get() based on the strings and raise a form validation error if you can't get it ... right, name = ... unique=True .. so something like (off the top of my head) ... :
def clean_bands(self):
return Band.objects.filter( name__in = self.cleaned_data['bands'].split(' ') )
You could also check each string and raise a form error if there are no bands by that name .. not sure that the clean method should return a qs. Let me know if this helps and you want me to keep going/clarify.