From 47434388d24595d5bea10c88420daaab179287f3 Mon Sep 17 00:00:00 2001 From: estevaofon Date: Mon, 27 Nov 2017 14:11:48 -0200 Subject: [PATCH 1/4] updated requirements --- code/requirements.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/code/requirements.txt b/code/requirements.txt index cc78215..86df05b 100644 --- a/code/requirements.txt +++ b/code/requirements.txt @@ -1,5 +1,6 @@ --f /usr/share/pip-wheels -Django==1.9.6 +Django==1.11.7 +Pillow==4.3.0 django-bootstrap-toolkit==2.15.0 -django-registration-redux==1.4 -Pillow==3.3.0 +django-registration-redux==1.9 +olefile==0.44 +pytz==2017.3 From 6821dbcf1e24acde94c3a222230b3965ab5d11e5 Mon Sep 17 00:00:00 2001 From: estevaofon Date: Mon, 27 Nov 2017 14:19:34 -0200 Subject: [PATCH 2/4] change default to py3 --- .../rango/webhose_search.py | 166 +++++++++--------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/code/tango_with_django_project/rango/webhose_search.py b/code/tango_with_django_project/rango/webhose_search.py index 3cf4429..e4ebb64 100644 --- a/code/tango_with_django_project/rango/webhose_search.py +++ b/code/tango_with_django_project/rango/webhose_search.py @@ -1,6 +1,7 @@ import json -import urllib -import urllib2 +import urllib.parse # Py3 +import urllib.request # Py3 + def read_webhose_key(): """ @@ -12,125 +13,126 @@ def read_webhose_key(): # Here we are using "with" when opening files. # http://docs.quantifiedcode.com/python-anti-patterns/maintainability/ webhose_api_key = None - try: with open('search.key', 'r') as f: webhose_api_key = f.readline().strip() except: raise IOError('search.key file not found') - + return webhose_api_key + def run_query(search_terms, size=10): """ - Given a string containing search terms (query), and a number of results to return (default of 10), - returns a list of results from the Webhose API, with each result consisting of a title, link and summary. + Given a string containing search terms (query), and a number of results to + return (default of 10), returns a list of results from the Webhose API, + with each result consisting of a title, link and summary. """ webhose_api_key = read_webhose_key() - if not webhose_api_key: raise KeyError('Webhose key not found') - - + # What's the base URL for the Webhose API? root_url = 'http://webhose.io/search' - # Format the query string - escape special characters. - query_string = urllib.quote(search_terms) - + query_string = urllib.parse.quote(search_terms) # Py3 # Use string formatting to construct the complete API URL. - search_url = '{root_url}?token={key}&format=json&q={query}&sort=relevancy&size={size}'.format( - root_url=root_url, - key=webhose_api_key, - query=query_string, - size=size) - + # search_url is a string split over multiple lines. + search_url = ('{root_url}?token={key}&format=json&q={query}' + '&sort=relevancy&size={size}').format( + root_url=root_url, + key=webhose_api_key, + query=query_string, + size=size) results = [] - try: - # Connect to the Webhose API, and convert the response to a Python dictionary. - print(search_url) - response = urllib2.urlopen(search_url).read() - print(response) + # Connect to the Webhose API, and convert the response to a + # Python dictionary. + response = urllib.request.urlopen(search_url).read().decode('utf-8') json_response = json.loads(response) - - # Loop through the posts, appendng each to the results list as a dictionary. + # Loop through the posts, appending each to the results list as + # a dictionary. We restrict the summary to the first 200 + # characters, as summary responses from Webhose can be long! for post in json_response['posts']: - print(post['title']) results.append({'title': post['title'], 'link': post['url'], 'summary': post['text'][:200]}) - except Exception as e: - print(e) + except: print("Error when querying the Webhose API") - + # Return the list of results to the calling function. return results + #### -#### The code below is the Python 3 version. +#### The code below is the Python 2 version. #### - -# import json -# import urllib.parse # Py3 -# import urllib.request # Py3 # -# def read_webhose_key(): -# """ -# Reads the Webhose API key from a file called 'search.key'. -# Returns either None (no key found), or a string representing the key. -# Remember: put search.key in your .gitignore file to avoid committing it! -# """ -# # See Python Anti-Patterns - it's an awesome resource! -# # Here we are using "with" when opening files. -# # http://docs.quantifiedcode.com/python-anti-patterns/maintainability/ -# webhose_api_key = None +#import json +#import urllib +#import urllib2 +# +#def read_webhose_key(): +# """ +# Reads the Webhose API key from a file called 'search.key'. +# Returns either None (no key found), or a string representing the key. +# Remember: put search.key in your .gitignore file to avoid committing it! +# """ +# # See Python Anti-Patterns - it's an awesome resource! +# # Here we are using "with" when opening files. +# # http://docs.quantifiedcode.com/python-anti-patterns/maintainability/ +# webhose_api_key = None +# +# try: +# with open('search.key', 'r') as f: +# webhose_api_key = f.readline().strip() +# except: +# raise IOError('search.key file not found') # -# try: -# with open('search.key', 'r') as f: -# webhose_api_key = f.readline().strip() -# except: -# raise IOError('search.key file not found') +# return webhose_api_key # -# return webhose_api_key +#def run_query(search_terms, size=10): +# """ +# Given a string containing search terms (query), and a number of results to return (default of 10), +# returns a list of results from the Webhose API, with each result consisting of a title, link and summary. +# """ +# webhose_api_key = read_webhose_key() # -# def run_query(search_terms, size=10): -# """ -# Given a string containing search terms (query), and a number of results to return (default of 10), -# returns a list of results from the Webhose API, with each result consisting of a title, link and summary. -# """ -# webhose_api_key = read_webhose_key() +# if not webhose_api_key: +# raise KeyError('Webhose key not found') # -# if not webhose_api_key: -# raise KeyError('Webhose key not found') # -# # What's the base URL for the Webhose API? -# root_url = 'http://webhose.io/search' +# # What's the base URL for the Webhose API? +# root_url = 'http://webhose.io/search' # -# # Format the query string - escape special characters. -# query_string = urllib.parse.quote(search_terms) # Py3 +# # Format the query string - escape special characters. +# query_string = urllib.quote(search_terms) # -# # Use string formatting to construct the complete API URL. -# search_url = '{root_url}?token={key}&format=json&q={query}&sort=relevancy&size={size}'.format( -# root_url=root_url, -# key=webhose_api_key, -# query=query_string, -# size=size) +# # Use string formatting to construct the complete API URL. +# search_url = '{root_url}?token={key}&format=json&q={query}&sort=relevancy&size={size}'.format( +# root_url=root_url, +# key=webhose_api_key, +# query=query_string, +# size=size) # -# results = [] +# results = [] # -# try: -# # Connect to the Webhose API, and convert the response to a Python dictionary. -# response = urllib.request.urlopen(search_url).read().decode('utf-8') #Py3 (library, decode) -# json_response = json.loads(response) +# try: +# # Connect to the Webhose API, and convert the response to a Python dictionary. +# print(search_url) +# response = urllib2.urlopen(search_url).read() +# print(response) +# json_response = json.loads(response) # -# # Loop through the posts, appendng each to the results list as a dictionary. -# for post in json_response['posts']: -# results.append({'title': post['title'], -# 'link': post['url'], -# 'summary': post['text'][:200]}) -# except: -# print("Error when querying the Webhose API") +# # Loop through the posts, appendng each to the results list as a dictionary. +# for post in json_response['posts']: +# print(post['title']) +# results.append({'title': post['title'], +# 'link': post['url'], +# 'summary': post['text'][:200]}) +# except Exception as e: +# print(e) +# print("Error when querying the Webhose API") # -# # Return the list of results to the calling function. -# return results \ No newline at end of file +# # Return the list of results to the calling function. +# return results From 85ee092ffcc0fb20adea313bbd9054aaedb63f5f Mon Sep 17 00:00:00 2001 From: estevaofon Date: Mon, 27 Nov 2017 14:20:23 -0200 Subject: [PATCH 3/4] Fix like button --- code/tango_with_django_project/static/js/rango-ajax.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/tango_with_django_project/static/js/rango-ajax.js b/code/tango_with_django_project/static/js/rango-ajax.js index 1547f48..135c15a 100644 --- a/code/tango_with_django_project/static/js/rango-ajax.js +++ b/code/tango_with_django_project/static/js/rango-ajax.js @@ -3,7 +3,7 @@ $('#likes').click(function(){ var catid; catid = $(this).attr("data-catid"); - $.get('/rango/like_category/', {category_id: catid}, function(data){ + $.get('/rango/like/', {category_id: catid}, function(data){ $('#like_count').html(data); $('#likes').hide(); }); From c7e0765baa7c45a10b4ebafc22cb497386ac2a68 Mon Sep 17 00:00:00 2001 From: estevaofon Date: Mon, 27 Nov 2017 14:24:48 -0200 Subject: [PATCH 4/4] Change URLField to Charfield to the form accept https format in the add page. --- code/tango_with_django_project/rango/forms.py | 13 ++-- .../rango/migrations/0001_initial.py | 51 ++++++++++++++ .../migrations/0002_auto_20171127_0412.py | 20 ++++++ .../rango/migrations/__init__.py | 0 .../tango_with_django_project/rango/models.py | 18 ++--- code/tango_with_django_project/rango/views.py | 70 ++++++++++--------- 6 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 code/tango_with_django_project/rango/migrations/0001_initial.py create mode 100644 code/tango_with_django_project/rango/migrations/0002_auto_20171127_0412.py create mode 100644 code/tango_with_django_project/rango/migrations/__init__.py diff --git a/code/tango_with_django_project/rango/forms.py b/code/tango_with_django_project/rango/forms.py index bdc039f..759b7d2 100644 --- a/code/tango_with_django_project/rango/forms.py +++ b/code/tango_with_django_project/rango/forms.py @@ -1,5 +1,7 @@ from django import forms from rango.models import Page, Category, UserProfile +from django.forms.widgets import TextInput +from django.core.validators import URLValidator class CategoryForm(forms.ModelForm): name = forms.CharField(max_length=128, help_text="Please enter the category name.") @@ -13,11 +15,13 @@ class Meta: model = Category fields = ('name',) + class PageForm(forms.ModelForm): title = forms.CharField(max_length=128, help_text="Please enter the title of the page.") - url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.") + url = forms.CharField(max_length=200, help_text="Please enter the URL of the page.", validators=[URLValidator()]) views = forms.IntegerField(widget=forms.HiddenInput(), initial=0) + """ def clean(self): cleaned_data = self.cleaned_data url = cleaned_data.get('url') @@ -27,7 +31,8 @@ def clean(self): url = 'http://' + url cleaned_data['url'] = url - return cleaned_data + return cleaned_data + """ class Meta: # Provide an association between the ModelForm and a model @@ -45,7 +50,7 @@ class Meta: class UserProfileForm(forms.ModelForm): website = forms.URLField(required=False) picture = forms.ImageField(required=False) - + class Meta: model = UserProfile - exclude = ('user',) \ No newline at end of file + exclude = ('user',) diff --git a/code/tango_with_django_project/rango/migrations/0001_initial.py b/code/tango_with_django_project/rango/migrations/0001_initial.py new file mode 100644 index 0000000..5e65104 --- /dev/null +++ b/code/tango_with_django_project/rango/migrations/0001_initial.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2017-11-26 20:41 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=128, unique=True)), + ('views', models.IntegerField(default=0)), + ('likes', models.IntegerField(default=0)), + ('slug', models.SlugField(unique=True)), + ], + options={ + 'verbose_name_plural': 'categories', + }, + ), + migrations.CreateModel( + name='Page', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=128)), + ('url', models.URLField()), + ('views', models.IntegerField(default=0)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rango.Category')), + ], + ), + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('website', models.URLField(blank=True)), + ('picture', models.ImageField(blank=True, upload_to='profile_images')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/code/tango_with_django_project/rango/migrations/0002_auto_20171127_0412.py b/code/tango_with_django_project/rango/migrations/0002_auto_20171127_0412.py new file mode 100644 index 0000000..8a7934f --- /dev/null +++ b/code/tango_with_django_project/rango/migrations/0002_auto_20171127_0412.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.7 on 2017-11-27 04:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rango', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='page', + name='url', + field=models.CharField(max_length=200), + ), + ] diff --git a/code/tango_with_django_project/rango/migrations/__init__.py b/code/tango_with_django_project/rango/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/tango_with_django_project/rango/models.py b/code/tango_with_django_project/rango/models.py index 3b243ed..a0ff337 100644 --- a/code/tango_with_django_project/rango/models.py +++ b/code/tango_with_django_project/rango/models.py @@ -9,34 +9,34 @@ class Category(models.Model): views = models.IntegerField(default=0) likes = models.IntegerField(default=0) slug = models.SlugField(unique=True) - + def save(self, *args, **kwargs): self.slug = slugify(self.name) super(Category, self).save(*args, **kwargs) - + class Meta: verbose_name_plural = 'categories' - + def __str__(self): return self.name class Page(models.Model): category = models.ForeignKey(Category) title = models.CharField(max_length=128) - url = models.URLField() + url = models.CharField(max_length=200) views = models.IntegerField(default=0) - + def __str__(self): return self.title - - + + class UserProfile(models.Model): # This line is required. Links UserProfile to a User model instance. user = models.OneToOneField(User) # The additional attributes we wish to include. website = models.URLField(blank=True) picture = models.ImageField(upload_to='profile_images', blank=True) - + # Override the __unicode__() method to return out something meaningful! def __str__(self): - return self.user.username \ No newline at end of file + return self.user.username diff --git a/code/tango_with_django_project/rango/views.py b/code/tango_with_django_project/rango/views.py index 3eb9e03..c1c0d10 100644 --- a/code/tango_with_django_project/rango/views.py +++ b/code/tango_with_django_project/rango/views.py @@ -37,7 +37,7 @@ def visitor_cookie_handler(request): request.session['last_visit'] = str(datetime.now()) else: visits = 1 - # set the last visit cookie + # set the last visit cookie request.session['last_visit'] = last_visit_cookie # update/set the visits cookie request.session['visits'] = visits @@ -47,25 +47,25 @@ def visitor_cookie_handler(request): def index(request): #context_dict = {'boldmessage': "Crunchie, creamy, cookie, candy, cupcake!"} - + request.session.set_test_cookie() - + category_list = Category.objects.order_by('-likes')[:5] - + page_list = Page.objects.order_by('-views')[:5] - + context_dict = {'categories': category_list, 'pages': page_list} - + visitor_cookie_handler(request) - + context_dict['visits'] = request.session['visits'] - + print(request.session['visits']) - + response = render(request, 'rango/index.html', context=context_dict) - + return response - + def about(request): if request.session.test_cookie_worked(): @@ -73,15 +73,15 @@ def about(request): request.session.delete_test_cookie() # To complete the exercise in chapter 4, we need to remove the following line # return HttpResponse("Rango says here is the about page. View index page") - + # and replace it with a pointer to ther about.html template using the render method return render(request, 'rango/about.html',{}) - + def show_category(request, category_name_slug): # Create a context dictionary which we can pass # to the template rendering engine. context_dict = {} - + try: # Can we find a category name slug with the given name? # If we can't, the .get() method raises a DoesNotExist exception. @@ -90,7 +90,7 @@ def show_category(request, category_name_slug): # Retrieve all of the associated pages. # Note that filter() returns a list of page objects or an empty list pages = Page.objects.filter(category=category) - + # Adds our results list to the template context under name pages. context_dict['pages'] = pages # We also add the category object from @@ -100,7 +100,7 @@ def show_category(request, category_name_slug): # We get here if we didn't find the specified category. # Don't do anything - - # the template will display the "no category" message for us. + # the template will display the "no category" message for us. except Category.DoesNotExist: context_dict['category'] = None context_dict['pages'] = None @@ -112,8 +112,9 @@ def show_category(request, category_name_slug): result_list = [] if request.method == 'POST': - query = request.POST['query'].strip() + query = request.POST.get('query') if query: + query = query.strip() # Run our Webhose function to get the results list! result_list = run_query(query) context_dict['query'] = query @@ -122,10 +123,10 @@ def show_category(request, category_name_slug): # Go render the response and return it to the client. return render(request, 'rango/category.html', context_dict) - - - - + + + + def add_category(request): form = CategoryForm() # A HTTP POST? @@ -147,8 +148,8 @@ def add_category(request): # Will handle the bad form (or form details), new form or no form supplied cases. # Render the form with error messages (if any). return render(request, 'rango/add_category.html', {'form': form}) - - + + def add_page(request, category_name_slug): try: category = Category.objects.get(slug=category_name_slug) @@ -172,8 +173,8 @@ def add_page(request, category_name_slug): context_dict = {'form':form, 'category': category} return render(request, 'rango/add_page.html', context_dict) - - + + def search(request): result_list = [] if request.method == 'POST': @@ -182,8 +183,8 @@ def search(request): # Run our Webhose function to get the results list! result_list = run_query(query) return render(request, 'rango/search.html', {'result_list': result_list}) - - + + def register(request): registered = False if request.method == 'POST': @@ -210,7 +211,7 @@ def register(request): # else: # user_form = UserForm() # profile_form = UserProfileForm() - + user_form = UserForm() profile_form = UserProfileForm() @@ -231,6 +232,7 @@ def track_url(request): page = Page.objects.get(id=page_id) page.views = page.views + 1 page.save() + print(page.url) return redirect(page.url) except: return HttpResponse("Page id {0} not found".format(page_id)) @@ -246,13 +248,13 @@ def register_profile(request): user_profile = form.save(commit=False) user_profile.user = request.user user_profile.save() - + return redirect('index') else: print(form.errors) context_dict = {'form':form} - + return render(request, 'rango/profile_registration.html', context_dict) class RangoRegistrationView(RegistrationView): @@ -265,10 +267,10 @@ def profile(request, username): user = User.objects.get(username=username) except User.DoesNotExist: return redirect('index') - + userprofile = UserProfile.objects.get_or_create(user=user)[0] form = UserProfileForm({'website': userprofile.website, 'picture': userprofile.picture}) - + if request.method == 'POST': form = UserProfileForm(request.POST, request.FILES, instance=userprofile) if form.is_valid(): @@ -276,11 +278,11 @@ def profile(request, username): return redirect('profile', user.username) else: print(form.errors) - + return render(request, 'rango/profile.html', {'userprofile': userprofile, 'selecteduser': user, 'form': form}) @login_required def list_profiles(request): # user_list = User.objects.all() userprofile_list = UserProfile.objects.all() - return render(request, 'rango/list_profiles.html', { 'userprofile_list' : userprofile_list}) \ No newline at end of file + return render(request, 'rango/list_profiles.html', { 'userprofile_list' : userprofile_list})