diff --git a/tango_with_django_project/manage.py b/tango_with_django_project/manage.py new file mode 100755 index 0000000..8af698f --- /dev/null +++ b/tango_with_django_project/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == '__main__': + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tango_with_django_project.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) diff --git a/tango_with_django_project/media/.keep b/tango_with_django_project/media/.keep new file mode 100644 index 0000000..1c25d00 --- /dev/null +++ b/tango_with_django_project/media/.keep @@ -0,0 +1 @@ +A blank file for ensuring that the media/ directory is included in the media commit. \ No newline at end of file diff --git a/tango_with_django_project/media/cat.jpg b/tango_with_django_project/media/cat.jpg new file mode 100644 index 0000000..6d7f751 Binary files /dev/null and b/tango_with_django_project/media/cat.jpg differ diff --git a/tango_with_django_project/populate_rango.py b/tango_with_django_project/populate_rango.py new file mode 100644 index 0000000..a14968c --- /dev/null +++ b/tango_with_django_project/populate_rango.py @@ -0,0 +1,71 @@ +import os +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tango_with_django_project.settings') + +import django +django.setup() +from rango.models import Category, Page + +# For an explanation of what is going on here, please refer to the TwD book. + +def populate(): + python_pages = [ + {'title': 'Official Python Tutorial', + 'url':'http://docs.python.org/3/tutorial/', + 'views': 114,}, + {'title':'How to Think like a Computer Scientist', + 'url':'http://www.greenteapress.com/thinkpython/', + 'views': 53}, + {'title':'Learn Python in 10 Minutes', + 'url':'http://www.korokithakis.net/tutorials/python/', + 'views': 20} ] + + django_pages = [ + {'title':'Official Django Tutorial', + 'url':'https://docs.djangoproject.com/en/2.1/intro/tutorial01/', + 'views': 32}, + {'title':'Django Rocks', + 'url':'http://www.djangorocks.com/', + 'views': 12}, + {'title':'How to Tango with Django', + 'url':'http://www.tangowithdjango.com/', + 'views': 1258} ] + + other_pages = [ + {'title':'Bottle', + 'url':'http://bottlepy.org/docs/dev/', + 'views': 54}, + {'title':'Flask', + 'url':'http://flask.pocoo.org', + 'views': 64} ] + + cats = {'Python': {'pages': python_pages, 'views': 128, 'likes': 64}, + 'Django': {'pages': django_pages, 'views': 64, 'likes': 32}, + 'Other Frameworks': {'pages': other_pages, 'views': 32, 'likes': 16} } + + for cat, cat_data in cats.items(): + c = add_cat(cat, views=cat_data['views'], likes=cat_data['likes']) + for p in cat_data['pages']: + add_page(c, p['title'], p['url'], views=p['views']) + + for c in Category.objects.all(): + for p in Page.objects.filter(category=c): + print(f'- {c}: {p}') + +def add_page(cat, title, url, views=0): + p = Page.objects.get_or_create(category=cat, title=title)[0] + p.url=url + p.views=views + p.save() + return p + +def add_cat(name, views=0, likes=0): + c = Category.objects.get_or_create(name=name)[0] + c.views = views + c.likes = likes + c.save() + return c + +# Start execution here! +if __name__ == '__main__': + print('Starting Rango population script...') + populate() \ No newline at end of file diff --git a/tango_with_django_project/rango/__init__.py b/tango_with_django_project/rango/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tango_with_django_project/rango/admin.py b/tango_with_django_project/rango/admin.py new file mode 100644 index 0000000..29a18f6 --- /dev/null +++ b/tango_with_django_project/rango/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from rango.models import Category, Page + +class PageAdmin(admin.ModelAdmin): + list_display = ('title', 'category', 'url') + +class CategoryAdmin(admin.ModelAdmin): + prepopulated_fields = {'slug': ('name',)} + +admin.site.register(Category, CategoryAdmin) +admin.site.register(Page, PageAdmin) \ No newline at end of file diff --git a/tango_with_django_project/rango/apps.py b/tango_with_django_project/rango/apps.py new file mode 100644 index 0000000..5b17213 --- /dev/null +++ b/tango_with_django_project/rango/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class RangoConfig(AppConfig): + name = 'rango' diff --git a/tango_with_django_project/rango/forms.py b/tango_with_django_project/rango/forms.py new file mode 100644 index 0000000..3e81c39 --- /dev/null +++ b/tango_with_django_project/rango/forms.py @@ -0,0 +1,33 @@ +from django import forms +from rango.models import Page, Category + +# We could add these forms to views.py, but it makes sense to split them off into their own file. + +class CategoryForm(forms.ModelForm): + name = forms.CharField(max_length=Category.NAME_MAX_LENGTH, help_text="Please enter the category name.") + views = forms.IntegerField(widget=forms.HiddenInput(), initial=0) + likes = forms.IntegerField(widget=forms.HiddenInput(), initial=0) + slug = forms.CharField(widget=forms.HiddenInput(), required=False) + + class Meta: + model = Category + fields = ('name',) + +class PageForm(forms.ModelForm): + title = forms.CharField(max_length=Page.TITLE_MAX_LENGTH, help_text="Please enter the title of the page.") + url = forms.URLField(max_length=200, help_text="Please enter the URL of the page.") + views = forms.IntegerField(widget=forms.HiddenInput(), initial=0) + + class Meta: + model = Page + exclude = ('category',) + + def clean(self): + cleaned_data = self.cleaned_data + url = cleaned_data.get('url') + + if url and not url.startswith('http://'): + url = f'http://{url}' + cleaned_data['url'] = url + + return cleaned_data \ No newline at end of file diff --git a/tango_with_django_project/rango/models.py b/tango_with_django_project/rango/models.py new file mode 100644 index 0000000..d0f69ee --- /dev/null +++ b/tango_with_django_project/rango/models.py @@ -0,0 +1,32 @@ +from django.db import models +from django.template.defaultfilters import slugify + +class Category(models.Model): + NAME_MAX_LENGTH = 128 + + name = models.CharField(max_length=NAME_MAX_LENGTH, unique=True) + 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): + TITLE_MAX_LENGTH = 128 + URL_MAX_LENGTH = 200 + + category = models.ForeignKey(Category, on_delete=models.CASCADE) + title = models.CharField(max_length=TITLE_MAX_LENGTH) + url = models.URLField() + views = models.IntegerField(default=0) + + def __str__(self): + return self.title \ No newline at end of file diff --git a/tango_with_django_project/rango/tests.py b/tango_with_django_project/rango/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/tango_with_django_project/rango/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/tango_with_django_project/rango/urls.py b/tango_with_django_project/rango/urls.py new file mode 100644 index 0000000..324c906 --- /dev/null +++ b/tango_with_django_project/rango/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from rango import views + +app_name = 'rango' + +urlpatterns = [ + path('', views.index, name='index'), + path('about/', views.about, name='about'), + path('category//', views.show_category, name='show_category'), + path('add_category/', views.add_category, name='add_category'), + path('category//add_page/', views.add_page, name='add_page'), +] \ No newline at end of file diff --git a/tango_with_django_project/rango/views.py b/tango_with_django_project/rango/views.py new file mode 100644 index 0000000..1697208 --- /dev/null +++ b/tango_with_django_project/rango/views.py @@ -0,0 +1,82 @@ +from django.shortcuts import render +from django.http import HttpResponse +from rango.models import Category +from rango.models import Page +from rango.forms import CategoryForm +from django.shortcuts import redirect +from django.urls import reverse +from rango.forms import PageForm + +def index(request): + category_list = Category.objects.order_by('-likes')[:5] + page_list = Page.objects.order_by('-views')[:5] + + context_dict = {} + context_dict['boldmessage'] = 'Crunchy, creamy, cookie, candy, cupcake!' + context_dict['categories'] = category_list + context_dict['pages'] = page_list + context_dict['extra'] = 'From the model solution on GitHub' + + return render(request, 'rango/index.html', context=context_dict) + +def about(request): + # Spoiler: you don't need to pass a context dictionary here. + return render(request, 'rango/about.html') + +def show_category(request, category_name_slug): + context_dict = {} + + try: + category = Category.objects.get(slug=category_name_slug) + pages = Page.objects.filter(category=category) + + context_dict['pages'] = pages + context_dict['category'] = category + except Category.DoesNotExist: + context_dict['pages'] = None + context_dict['category'] = None + + return render(request, 'rango/category.html', context=context_dict) + +def add_category(request): + form = CategoryForm() + + if request.method == 'POST': + form = CategoryForm(request.POST) + + if form.is_valid(): + form.save(commit=True) + return redirect('/rango/') + else: + print(form.errors) + + 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) + except: + category = None + + # You cannot add a page to a Category that does not exist... DM + if category is None: + return redirect('/rango/') + + form = PageForm() + + if request.method == 'POST': + form = PageForm(request.POST) + + if form.is_valid(): + if category: + page = form.save(commit=False) + page.category = category + page.views = 0 + page.save() + + return redirect(reverse('rango:show_category', kwargs={'category_name_slug': category_name_slug})) + else: + print(form.errors) # This could be better done; for the purposes of TwD, this is fine. DM. + + context_dict = {'form': form, 'category': category} + return render(request, 'rango/add_page.html', context=context_dict) \ No newline at end of file diff --git a/tango_with_django_project/static/images/rango.jpg b/tango_with_django_project/static/images/rango.jpg new file mode 100644 index 0000000..5835b53 Binary files /dev/null and b/tango_with_django_project/static/images/rango.jpg differ diff --git a/tango_with_django_project/tango_with_django_project/__init__.py b/tango_with_django_project/tango_with_django_project/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tango_with_django_project/tango_with_django_project/settings.py b/tango_with_django_project/tango_with_django_project/settings.py new file mode 100644 index 0000000..60a849c --- /dev/null +++ b/tango_with_django_project/tango_with_django_project/settings.py @@ -0,0 +1,128 @@ +""" +Django settings for tango_with_django_project project. + +Generated by 'django-admin startproject' using Django 2.1.5. + +For more information on this file, see +https://docs.djangoproject.com/en/2.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.1/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates') +STATIC_DIR = os.path.join(BASE_DIR, 'static') +MEDIA_DIR = os.path.join(BASE_DIR, 'media') + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'sq_z*m=yr)aiu$9lyzf@)&-!o(yjs=)9igwhuco$d7@a3jed=@' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rango', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'tango_with_django_project.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [TEMPLATE_DIR, ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.media', + ], + }, + }, +] + +WSGI_APPLICATION = 'tango_with_django_project.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.1/howto/static-files/ + +STATIC_URL = '/static/' +STATICFILES_DIRS = [STATIC_DIR, ] + +MEDIA_ROOT = MEDIA_DIR +MEDIA_URL = '/media/' \ No newline at end of file diff --git a/tango_with_django_project/tango_with_django_project/urls.py b/tango_with_django_project/tango_with_django_project/urls.py new file mode 100644 index 0000000..30c8adb --- /dev/null +++ b/tango_with_django_project/tango_with_django_project/urls.py @@ -0,0 +1,27 @@ +"""rango_project URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include +from rango import views +from django.conf import settings +from django.conf.urls.static import static + + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', views.index, name="index"), + path('rango/', include('rango.urls', namespace="rango")), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/tango_with_django_project/tango_with_django_project/wsgi.py b/tango_with_django_project/tango_with_django_project/wsgi.py new file mode 100644 index 0000000..d3a1b1b --- /dev/null +++ b/tango_with_django_project/tango_with_django_project/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for tango_with_django_project project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tango_with_django_project.settings') + +application = get_wsgi_application() diff --git a/tango_with_django_project/templates/rango/.hidden b/tango_with_django_project/templates/rango/.hidden new file mode 100644 index 0000000..f934a67 --- /dev/null +++ b/tango_with_django_project/templates/rango/.hidden @@ -0,0 +1 @@ +This is a hidden file. If you're a student doing this course for credit, cloning this repository won't get you very far. There's subtle things placed here and there that can prove to an astute professor that you just did that. Better to go and read through the book by yourself. \ No newline at end of file diff --git a/tango_with_django_project/templates/rango/about.html b/tango_with_django_project/templates/rango/about.html new file mode 100644 index 0000000..3d13142 --- /dev/null +++ b/tango_with_django_project/templates/rango/about.html @@ -0,0 +1,24 @@ + + +{% load staticfiles %} + + + + + Rango + + + +

Rango says...

+
+ here is the about page.
+ This tutorial has been put together by David Maxwell.
+
+
+ Index
+ Picture of Rango + Picture of a Cat +
+ + + \ No newline at end of file diff --git a/tango_with_django_project/templates/rango/add_category.html b/tango_with_django_project/templates/rango/add_category.html new file mode 100644 index 0000000..1bdd32b --- /dev/null +++ b/tango_with_django_project/templates/rango/add_category.html @@ -0,0 +1,25 @@ + + + + + Rango + + + +

Add a Category

+
+
+ {% csrf_token %} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + {% for field in form.visible_fields %} + {{ field.errors }} + {{ field.help_text }} + {{ field }} + {% endfor %} + +
+
+ + \ No newline at end of file diff --git a/tango_with_django_project/templates/rango/add_page.html b/tango_with_django_project/templates/rango/add_page.html new file mode 100644 index 0000000..4c5edfb --- /dev/null +++ b/tango_with_django_project/templates/rango/add_page.html @@ -0,0 +1,26 @@ + + + + + Rango + + + +

Add a Page to {{ category.name }}

+
+
+ {% csrf_token %} + {% csrf_token %} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + {% for field in form.visible_fields %} + {{ field.errors }} + {{ field.help_text }} + {{ field }} + {% endfor %} + +
+
+ + \ No newline at end of file diff --git a/tango_with_django_project/templates/rango/category.html b/tango_with_django_project/templates/rango/category.html new file mode 100644 index 0000000..5a28dee --- /dev/null +++ b/tango_with_django_project/templates/rango/category.html @@ -0,0 +1,32 @@ + + + + + + Rango + + + +
+ {% if category %} +

{{ category.name }}

+ {% if pages %} + + {% else %} + No pages currently in category. + {% endif %} + + Add Page
+ {% else %} + The specified category does not exist. + {% endif %} +
+ + Index + + + \ No newline at end of file diff --git a/tango_with_django_project/templates/rango/index.html b/tango_with_django_project/templates/rango/index.html new file mode 100644 index 0000000..632e026 --- /dev/null +++ b/tango_with_django_project/templates/rango/index.html @@ -0,0 +1,51 @@ + + +{% load staticfiles %} + + + + + Rango + + + +

Rango says...

+
+ hey there partner!
+ {{ boldmessage }}
+
+ +
+

Most Liked Categories

+ {% if categories %} + + {% else %} + There are no categories present. + {% endif %} +
+ +
+

Most Viewed Pages

+ {% if pages %} + + {% else %} + There are no pages present. + {% endif %} +
+ +
+ About
+ Add a New Category
+ Picture of Rango +
+ + + \ No newline at end of file