diff --git a/3 JavaScript/demo/vueIntro/index.css b/3 JavaScript/demo/vueIntro/index.css new file mode 100644 index 00000000..bbb2fadf --- /dev/null +++ b/3 JavaScript/demo/vueIntro/index.css @@ -0,0 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap'); + +* { + font-family: 'Montserrat', sans-serif; +} \ No newline at end of file diff --git a/3 JavaScript/demo/vueIntro/index.html b/3 JavaScript/demo/vueIntro/index.html new file mode 100644 index 00000000..2c4fed58 --- /dev/null +++ b/3 JavaScript/demo/vueIntro/index.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + Vue Intro + + + + +
+

{{ headerText }}

+ + +

+ + + + The number {{ counter }} is + + Even + Odd +

+ +
+
+ {{ randomNumber }} is + + less than 5 + equal to 5 + greater than 5 +
+

Previous numbers:

+
    + +
  • {{number}}
  • +
+
+
+ + +
{{userData}}
+ +
+ + + + + + \ No newline at end of file diff --git a/4 Django/demo/drf_vue_todo_app/vueTodoApp.html b/4 Django/demo/drf_vue_todo_app/vueTodoApp.html new file mode 100644 index 00000000..accde9b5 --- /dev/null +++ b/4 Django/demo/drf_vue_todo_app/vueTodoApp.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + Vue To Do + + + +
+

{{ headerText }}

+ +
+ +
+ + +
+ + +
+

Incomplete

+
+ {{todo.id}}. {{todo.text}} +
+ + +
+
+
+ + +
+

Complete

+
+ + {{todo.id}}. {{todo.text}} + +
+ + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/4 Django/demo/ecommerce/shop_app/views.py b/4 Django/demo/ecommerce/shop_app/views.py index 925de30e..e1a753e0 100644 --- a/4 Django/demo/ecommerce/shop_app/views.py +++ b/4 Django/demo/ecommerce/shop_app/views.py @@ -2,46 +2,110 @@ from django.contrib.auth.decorators import login_required from .models import Category, Product, Cart, CartItem from django.db.models import Q +from django.core.paginator import Paginator + +def index(request, page_num=1, per_page=5): + + + # if URL parameters exist for page_num or per_page, use those values + # otherwise, use the defaults from the index view + page_num = request.GET.get('page_num') or page_num + per_page = request.GET.get('per_page') or per_page -def index(request): - # sort products by ascending price - # products = Product.objects.all().order_by('price') + # create a dictionary with the values from the form if they exist or defaults if they don't + form = { + 'search_query': request.POST.get('search_query') or '', + 'order_by': request.POST.get('order_by') or 'price', + 'checked_categories': request.POST.getlist('categories') or [category.title for category in Category.objects.all()], + 'min_price': request.POST.get('min_price') or '', + 'max_price': request.POST.get('max_price') or '', + 'min_rating': request.POST.get('min_rating') or '', + 'max_rating': request.POST.get('max_rating') or '', + } - # sort all products by descending price - products = Product.objects.all().order_by('-price') + # pull the values from the form + search_query = form.get('search_query') + checked_categories = form.get('checked_categories') + min_price = form.get('min_price') + max_price = form.get('max_price') + min_rating = form.get('min_rating') + max_rating = form.get('max_rating') + order_by = form.get('order_by') - # category = Category.objects.get(title='jewelery') + # get all products + products = Product.objects.all() - # find all categories in a list of values - # categories = Category.objects.filter(title__in=['jewelery', 'electronics']) + # + # Apply all the filters based on which form fields had values when the form was submitted + if search_query: + # find the products whose title or description contain the search query + products = products.filter( + Q(title__icontains=search_query) | Q(description__icontains=search_query) + ) - # filters can be chained - # categories = Category.objects.filter(title__in=['jewelery', 'electronics']).filter(title__startswith='e') - - # find all the products whose categories list contains the title 'electronics' - # other_model__field_name__lookup - # products = Product.objects.filter(categories__title__icontains='electronics') + # find only the products in the given categories + products = products.filter(categories__title__in=checked_categories) - # __lte is less than or equal to for the given field - # products = Product.objects.filter(price__lte=100).order_by('price') - # __gte is less than or equal to for the given field - # products = Product.objects.filter(price__gte=100).order_by('price') + # apply price filters + if min_price and not max_price: + # find products whose price is greater than the min + products = products.filter(price__gte=min_price) - # use __gte and __lte to search within a range - # products = Product.objects.filter(price__gte=100, price__lte=200).order_by('price') + if max_price and not min_price: + # find products whose price is greater than the min + products = products.filter(price__lte=max_price) - # __startswith finds all items with whose value - # for a particular field starts with the given string - # products = Product.objects.filter(title__startswith='f') + if max_price and min_price: + # find products whose price is greater than the min + products = products.filter(price__gte=min_price, price__lte=max_price) - # Q objects allow OR | and AND & operations with queries - # products = Product.objects.filter( - # Q(title__startswith='f') | Q(title__startswith='s') - # ) - return render(request, 'shop/index.html', {'products': products}) + # apply rating filters + if min_rating and not max_rating: + # find products whose price is greater than the min + products = products.filter(rating__gte=min_rating) + + if max_rating and not min_rating: + # find products whose price is greater than the min + products = products.filter(rating__lte=max_rating) + + if max_rating and min_rating: + # find products whose price is greater than the min + products = products.filter(rating__gte=min_rating, rating__lte=max_rating) + + # sort the remaining products + products = products.order_by(order_by) + + + # create a page object from the remaining products + products_page = Paginator(products, per_page).get_page(page_num) + + # print(products_page.num_pages) + # print(products_page.page_range) + + # data for rendering 'categories' checkboxes and 'order by' select menu + search_options = { + 'categories': [category.title for category in Category.objects.all()], + 'order_by_options': [ + {'label': 'Price (Low-to-High)', 'value': 'price'}, + {'label': 'Price (High-to-Low)', 'value': '-price'}, + {'label': 'Name (A-Z)', 'value': 'title'}, + {'label': 'Name (Z-A)', 'value': '-title'}, + {'label': 'Rating (Low-to-High)', 'value': 'rating'}, + {'label': 'Rating (High-to-Low)', 'value': '-rating'}, + ] + } + + # pack it up in a dictionary and ship it to the template + context = { + 'products_page': products_page, + 'search_options': search_options, + 'form': form + } + + return render(request, 'shop/index.html', context) @login_required @@ -78,3 +142,41 @@ def remove_from_cart(request, cart_item_id): cart_item = get_object_or_404(CartItem, id=cart_item_id) cart_item.delete() return redirect(reverse('users_app:detail', kwargs={'username': request.user.username})) + + + + + +#Queries +# sort products by ascending price +# products = Product.objects.all().order_by('price') + +# category = Category.objects.get(title='jewelery') + +# find all categories in a list of values +# categories = Category.objects.filter(title__in=['jewelery', 'electronics']) + +# filters can be chained +# categories = Category.objects.filter(title__in=['jewelery', 'electronics']).filter(title__startswith='e') + +# find all the products whose categories list contains the title 'electronics' +# other_model__field_name__lookup +# products = Product.objects.filter(categories__title__icontains='electronics') + +# __lte is less than or equal to for the given field +# products = Product.objects.filter(price__lte=100).order_by('price') + +# __gte is less than or equal to for the given field +# products = Product.objects.filter(price__gte=100).order_by('price') + +# use __gte and __lte to search within a range +# products = Product.objects.filter(price__gte=100, price__lte=200).order_by('price') + +# __startswith finds all items with whose value +# for a particular field starts with the given string +# products = Product.objects.filter(title__startswith='f') + +# Q objects allow OR | and AND & operations with queries +# products = Product.objects.filter( +# Q(title__startswith='f') | Q(title__startswith='s') +# ) \ No newline at end of file diff --git a/4 Django/demo/ecommerce/templates/base.html b/4 Django/demo/ecommerce/templates/base.html index fe0f812c..078ae48a 100644 --- a/4 Django/demo/ecommerce/templates/base.html +++ b/4 Django/demo/ecommerce/templates/base.html @@ -2,6 +2,7 @@ + @@ -10,40 +11,35 @@ - + - - + + eCommerce + {% csrf_token %} {% include 'partials/_navbar.html' %}
- {% block content %} - - {% endblock content %} +
+ {% block content %} + + {% endblock content %} +
+ \ No newline at end of file diff --git a/4 Django/demo/ecommerce/templates/shop/_product-filters.html b/4 Django/demo/ecommerce/templates/shop/_product-filters.html new file mode 100644 index 00000000..df1f5f0c --- /dev/null +++ b/4 Django/demo/ecommerce/templates/shop/_product-filters.html @@ -0,0 +1,85 @@ +
+
+ + +
+
Query
+ +
+ + +
+
Order By
+ +
+ + +
+
Categories
+ {% for category in search_options.categories %} + + {% endfor %} +
+ + + +
+
Price
+ + +
+ + + +
+
Rating
+ + +
+ + {% csrf_token %} + + +
+
\ No newline at end of file diff --git a/4 Django/demo/ecommerce/templates/shop/index.html b/4 Django/demo/ecommerce/templates/shop/index.html index 7d853814..41c9105a 100644 --- a/4 Django/demo/ecommerce/templates/shop/index.html +++ b/4 Django/demo/ecommerce/templates/shop/index.html @@ -2,45 +2,84 @@ {% block content %} -
- - - {% for product in products %} -
-
- {{image.title}} - -
-
{{product.title}}
-

- Rated {{product.rating}} / 5 by {{product.rating_count}} users -

- -

${{product.price}}

- -

- {{product.description|truncatewords:20}} -

- -

- {% for category in product.categories.all %} -

- {{category|title}} -
- {% endfor %} -

-
- - + + +{% include 'shop/_product-filters.html' %} + + +{% for product in products_page %} +
+
+ {{image.title}} + +
+
{{product.title}}
+

+ Rated {{product.rating}} / 5 by {{product.rating_count}} users +

+ +

${{product.price}}

+ +

+ {{product.description|truncatewords:20}} +

+ +

+ {% for category in product.categories.all %} +

+ {{category|title}}
+ {% endfor %} +

+
+ + - {% endfor %} +
- +{% endfor %} + + + {% endblock content %} \ No newline at end of file diff --git a/5 Capstone/Capstone Ideas.md b/5 Capstone/Capstone Ideas.md new file mode 100644 index 00000000..ece6c942 --- /dev/null +++ b/5 Capstone/Capstone Ideas.md @@ -0,0 +1,68 @@ + +# Capstone Ideas + +## General Capstone Ideas + +- Blog +- Dating Site +- Twitter Clone +- Codepen/JSFiddle Clone +- Stack Overflow Clone +- Chatting App + +## Professional Sites Made with Django and/or Python +A list of some can be found [here](https://www.shuup.com/blog/25-of-the-most-popular-python-and-django-websites/) + + +## Past Capstones + +### 20171127 Full Stack Day + +- **Citizen Caucus** is an aggregate of information on the US Senate. It features profiles of senators, including their photo, contact information, and voting history on past bills. The data is taken from the [ProPublica API](https://www.propublica.org/) and [Sunlight API](https://sunlightfoundation.com/api/). It also has search with suggestions. By Ronnie Mosley [screenshot](Past%20Capstone%20Screenshots/citizen_caucus.png) +- **Tit4Tat** is, at its most basic concept, designed for people to exchange time for services rendered rather than money. In other words, a person (in my case a filmmaker) will post a project that they have in mind and is looking for help. By looking through other projects they can communicate to the other creator and then commit to each others idea if agreeable. User profiles include skills (type and level), interests, and equipment. By Remington Hendrickson [screenshot](Past%20Capstone%20Screenshots/tit4tat.jpg) +- **Hardcore Lives** keeps track of the local Hardcore shows and bands in Portland. The front page has an instragram embed, a carousel of local venues, and search. There's also a social media component, you can whether you're attending a show and upload flyers of upcoming shows. On the back-end, Beautiful Soup is used to scrape 8 different websites for shows, collecting their title, location, and date. By Michael. +- **Write 2 Learn** features tools to automatically provide feedback to students learning English. Users can make blog posts. One section lets them visualize the parts of speech (noun, verb, adjective) of their posts. Another section highlights any errors that they made. Users can also view charts of their error counts over time and the different counts of the parts of speech in their posts. There's also a page which shows the parts of speech in real time using NLTK. It uses several APIs including the Google Language API. By Ryan. +- **Chanterelle** allows users to write a Todo list with ordered events, represented as a directed acyclic graph. The main view uses [Cytoscape](http://js.cytoscape.org/) to visualize the nodes and edges of the graph. A "next up" list shows only the immediate todo items. Redundant edges are already removed (transitive reduction), as well as cycles. The back-end uses Django Rest Framework and the front-end is in React. By Emyli Poltorak. [screenshot 1](Past%20Capstone%20Screenshots/chanterelle1.png) [2](Past%20Capstone%20Screenshots/chanterelle2.png) [repo](https://github.com/emylipoltorak/Chanterelle) +- Time and task tracking based on the Pomodoro Technique, with a visualization of break and focus periods and timeline using [vis.js](http://visjs.org/). +- **Viewsic** connects to your Spotify account to show the artists, genres, and songs you play most often. It also shows the overall scores of your music by happiness, dancibility, and energy, and compares them to the average. By Matthew. [screenshot 1](Past%20Capstone%20Screenshots/viewsic01-cover.png) [2](Past%20Capstone%20Screenshots/viewsic02-top_artists.png) [3](Past%20Capstone%20Screenshots/viewsic03-genres.png) [4](Past%20Capstone%20Screenshots/viewsic04-top_songs.png) [5](Past%20Capstone%20Screenshots/viewsic05-popularity.png) [6](Past%20Capstone%20Screenshots/viewsic06-valence_index.png) [repo](https://github.com/Matt-Brown503/viewsic) + + +### 20171003 Full Stack Day + +- **Molecule**: group messaging, with login and group management. By Zia Dawn. [repo](https://github.com/ziadawn/Capstone) +- **Hauto**: browse vehicle parts by make, model, system, and manufacturer. By Allen Matasy. [repo](https://github.com/Spitfire22/hauto) +- hivemind: Q&A app, similar to Stack Overflow with accounts, questions, answers, and voting. By David Pennington. [repo](https://github.com/mootfowl/jubilant-octo-garbanzo) +- **Doggonnit**: posting and reporting lost dogs, with dog profiles and visualization of location data using Mapbox. By Marcel Schaeffer. [repo](https://github.com/mschaeffer53/Doggonnit) +- **FreshPoint**: Find what produce is in season, near you, right now. Database with over 150 pieces of produce and season data for all 50 states. Built REST API in Django to connect to Vue.js. Used Google GeoCoding, GeoChart and Edemam APIs. Built with Django and Vue.js. By Cameron Mirza. [screenshot 1](Past%20Capstone%20Screenshots/freshpoint1.jpg), [2](Past%20Capstone%20Screenshots/freshpoint2.jpg), [repo](https://github.com/cmirza/FreshPoint/tree/vue.js) +- **RUNdo**: calculate how long you'd have to run or walk to make up for the food you're about to eat. By Terri Stefan. [repo](https://github.com/tntterri615/RunDo) +- **Kidfolio**: parents can upload save pictures of their children's artwork. By Chloe Elliott. +- **Music To Video**: use Spotify and Youtube APIs to find videos for playlists. By Brandon Perard. [repo](https://github.com/bperard/Music-To-Video) +- **Loops**: find the quickest route through a series of waypoints, using Google Maps. By Kyle Douglas. [repo](https://github.com/kyledgls/loops) + + +### 20170724 Full Stack Night + +- **Food Access** a site which uses d3.js to visualize food access with a choropleth map. It loads an XLSX from [the Food Environment Atlas from the USDA Economic Research Service](https://www.ers.usda.gov/data-products/food-environment-atlas/data-access-and-documentation-downloads/) into a database. The front page then loads the data from the database via AJAX and uses it to populate the map. The front page has a drop-down to select which variable to visualize. There's another page to visualize the correlation matrix between the various variables, and another to see a scatter plot between two selected variables. By Jessica Pavlinko. [screenshot 1](Past%20Capstone%20Screenshots/food_access0.png), [2](Past%20Capstone%20Screenshots/food_access1.png), [3](Past%20Capstone%20Screenshots/food_access2.png), [repo](https://github.com/jpavs1010/pdxcodeguild/tree/master/capstone_project) +- **Brain Exchange** a site which matches teachers and students. When users register on the site, they list which skills they know (and are willing to teach) and which skills they want to learn. After registering, the user is matched up with those who can teach what they want to learn, and learn what they want to teach. By Rudolf Valentine [screenshot 1](Past%20Capstone%20Screenshots/brain_exchange1.png), [repo](https://github.com/IcarusSprite/profile/tree/master/Capstone) +- **Space Explorer** a game in d3.js like Flappy Bird, except you're an astronaut dodging asteroids and collecting space crystals. The user enters their name at the start, and when they finish, their score is entered into a table of high scores. By Samantha Moad. [repo](https://github.com/smoad/pdxcodeguild/tree/master/capstone/capstonesite) + + +### 20170626 Day Class + +- Musilux: you can upload a midi file, the application will parse it and begin playing the notes (audio) while displaying the notes played on a keyboard. By Roger Scherer. + + + + + +### Other Past Capstones + +- Disc Caddy is a Django based app that allows disc golfers to keep a virtual version of their disc bag, search local courses, and to see and provide updates on course conditions. You can also check-in to a hole, rate it for playability, leave comments, and post your score. Users can leave comments on a friends' check-in, or just give them a high five. If you provide your score and what disc you threw, Disc Caddy will offer disc suggestions based on what is in your bag and how your score is trending. By Kasey Watters + +- RunAround is a GeoDjango project that automatically builds runs for you based on the most popular runs for your area. You can find safe, popular runs even when you're in new cities or spots you aren't familiar with. It utilizes the Strava API to grab the most popular segments, decomposes them into points ranked by popularity, and then sends those points to the Google Maps API to build a route. Finally, when you've selected the run you'd like, it texts the route to you so you can open it in Google Maps for hands-free turn-by-turn directions on your phone while you run. By Michael Jolliffe + +### [Here's some images of past capstones](https://github.com/PdxCodeGuild/PythonFullStack2/tree/master/5%20Capstone/Past%20Capstone%20Screenshots) diff --git a/5 Capstone/Capstone Proposal.md b/5 Capstone/Capstone Proposal.md new file mode 100644 index 00000000..36e7e2a9 --- /dev/null +++ b/5 Capstone/Capstone Proposal.md @@ -0,0 +1,27 @@ + +# Capstone Proposal + +The Capstone is a web application that touches on every major technology we covered: Python, HTML, CSS, JavaScript, and Django. Below are the criteria for your proposal. I can help you sculpt out an idea, and I'll tell you very plainly whether a goal is attainable given our time constraints. I highly recommend doing some sketches of pages. This document is for you as much as it is for me. By planning thoroughly and precisely, the implementation will be much easier. Please do not change your idea after we start working on our capstones, it wastes your time and your end result won't be as good. You can use a wireframe/diagram tool like [diagrams.net](https://www.diagrams.net/). Your proposal is due by the time we start our capstones (3-4 weeks before the end of the course). + +- Your proposal must be in a markdown `.md` file [more info](https://help.github.com/articles/basic-writing-and-formatting-syntax/) +- Your proposal must set specific and attainable goals +- Your proposal must cover all major topics we've covered +- Your proposal must include the sections below + +## Project Overview + +What are the major features of your web application? What problem is it attempting to solve? What libraries or frameworks will you use? + +## Functionality + +Walk through the application from the user's perspective. What will they see on each page? What can they input and click and see? How will their actions correspond to events on the back-end? + +## Data Model + +What data will you need to store as part of your application? In this section list out all the models you'll have and the fields on each of them. + +## Schedule + +Here you'll want to come up with some (very rough) estimates of the timeframe for each section. It's difficult to be certain how long each feature will take to develop, therefore you should plan out multiple 'milestones' for your project. That way, if you reach milestone 2 but not 3, you still have something worthwhile to present and be proud of. It also gives you the opportunity to plan out what you'd like to work on after the class is finished. For each milestone, state specifically which steps you'll take in the implementation. This section should also include work you're planning to do after the capstone is finished. + + diff --git a/5 Capstone/Heroku Deployment.md b/5 Capstone/Heroku Deployment.md new file mode 100644 index 00000000..1b36a7fe --- /dev/null +++ b/5 Capstone/Heroku Deployment.md @@ -0,0 +1,236 @@ +# Deploy a Django Project on Heroku + +## Setup + +1. Preparation +A few good organizational practices will make this entire process much smoother. + - Your Django project *must* be in its own git repo. + - Your Django project *should* be at the root of that repo (The `manage.py` file and hidden `.git` folder should be in the same directory.) + - Your Django project *should* be developed in a virtual environment with `pipenv` or `venv`. + +2. Create a Heroku Account +Go to [heroku.com](https://www.heroku.com) and create a free account. + +3. Download the Heroku CLI +Follow the instructions at [devcenter.heroku.com](https://devcenter.heroku.com/articles/heroku-cli) to download the Heroku CLI for your operating system. + +## Create a new Heroku App +In a terminal, `cd` into the root of your project's git repo. +Run this command: +```console +$ heroku login +``` +The `heroku login` command will: +1. Verify that the Heroku CLI is installed +2. Log into your Heroku account from the terminal + +This should open Heroku in your browser. Follow the prompts to log in. + +#### [Create an app from the command line](https://devcenter.heroku.com/articles/git#for-a-new-heroku-app) +To create your app, run this command, replacing `[your-app-name]` with your app's name: +```console +$ heroku create [your-app-name] +``` + +### Note: +There is a chance that the name you want is taken. Try variations of your preferred name until you get one that isn't taken. +Once deployed, your app will be hosted at `https://your-app-name.herokuapp.com/` but you can set up a custom domain (i.e.: `your-app-name.com`, `your-app-name.net`) if you buy the domain from a DNS (Domain Name System) service like [Namecheap](https://www.namecheap.com) or [Google Domains](https://domains.google.com). + +## Heroku & Git +Heroku deploys your projects by building them up from git pushes to a remote repo. +Run the command `git remote -v`: + +```console +$ git remote -v +heroku https://git.heroku.com/your-app-name.git (fetch) +heroku https://git.heroku.com/your-app-name.git (push) +origin https://github.com/your-username/your-app-name.git (fetch) +origin https://github.com/your-username/your-app-name.git (push) +``` +You should see 4 lines of feedback from this command (two for Heroku, and two for GitHub). If you only see the `origin` lines, run this command to add the Heroku remote: +```console +$ heroku git:remote -a [your-app-name] +set git remote heroku to https://git.heroku.com/your-app-name.git +``` + +Run `git remote -v` again to verify that the Heroku remote has been added. + +## Python Environments & Deployment + +One of the first things that happens after you push a project to Heroku is buildpack detection. Heroku needs to know: + +1. What language is this project in? (Python) +2. Which dependencies (libraries & packages) does Heroku need to install in order to run your project? (Django, Pillow, DRF, etc.) + +There are multiple ways to give Heroku this information, and they each involve a file at the root of your git repo: + +### 1) `requirements.txt` +A requirements.txt file contains a list of all of your project's python dependencies. For example, if you are using Django 3.2.5 and Pillow 8.3.1, your requirements.txt looks like this: +``` +django==3.2.5 +pillow==8.3.1 +``` +A requirements.txt file can be generated with the `pip freeze` command. Be sure you are inside your virtual environment **and** at the root of your repo (where the hidden `.git` folder is), and run this command: +```console +$ pip freeze > requirements.txt +``` +This will fill requirements.txt with your Python dependencies (creating the file if needed). + +### 2) `Pipfile` and `Pipfile.lock` +If you are using `pipenv` for your virtual environment, install dependencies with `pipenv install` instead of `pip install` to create a Pipfile and Pipfile.lock. Here is what a Pipfile looks like: + +``` +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +django = "*" +pillow = "*" + +[dev-packages] + +[requires] +python_version = "3.9" +``` + +Notice the * in the versioning: `django = "*"`. That means that a specific version of Django wasn't specified. The Pipfile.lock, however, does keep track of which version of Django has been used in your development environment, and that is the one which Heroku will install when it builds up your project. + +If you've been using `pipenv install` to add each dependency, you shouldn't have to do anything extra (like generating a requirements.txt file). Make sure that each package you are using is in the `[packages]` section of your Pipfile. (Any dependencies of those packages will be included in the Pipfile.lock) + +## `django-on-heroku` and `gunicorn` + +Things are always going to be different in a deployment/production enviroment than in a development environment. There are 3 main things that don't work the same way in a django project deployed to Heroku: +1. `python manage.py runserver` will no longer be used to run your project. That command is used for local development. You will use `gunicorn` to run your project via its wsgi file (more on this later). +2. Similarly, your static files (images, JavaScript, CSS) are served differently in production than in development. +3. Your `sqlite` database will not work on Heroku, at least not in an practical way. Each time a new version of your app is deployed, a new "dyno" is created, always built up from the git repo. If you run `python manage.py migrate` on your deployed app, it would make a `db.sqlite3` file, which would only persist until a new dyno is spun up: every time you push updates to Heroku, **OR** every time your Heroku app goes to sleep (very often if you are using the free version). The solution is to use PostgreSQL, which is very easily done with one of these new dependencies. + +In fact, [django-on-heroku](https://github.com/pkrefta/django-on-heroku), is going to make a lot of this very simple. Add two new dependencies to your project: +```console +$ pipenv install django-on-heroku gunicorn # if you are using pipenv and a Pipfile +$ pip install django-on-heroku gunicorn # if you are using requirements.txt +``` + +And make one change to your `settings.py`, add these lines to the end of the file: +```py +import django_on_heroku +django_on_heroku.settings(locals()) +``` + +The `django-on-heroku` package and these two lines are going to take care of the database problem and the static file problem, just like that. + +## The Heroku Procfile + +The Procfile (process file) is a special Heroku file that tells Heroku which web processes it needs to run to serve your app. So create a Procfile at the root of your repo (where the .git folder is). Hopefully, this is also where your `manage.py` file is. +Here, I'm using the `tree` command to see that this demo project has `Pipfile`, `Pipfile.lock`, `Procfile` and `manage.py` files, and a `django_demo` project folder with additional files: + +```console +$ tree +. +├── Pipfile +├── Pipfile.lock +├── Procfile +├── django_demo +│   ├── __init__.py +│   ├── asgi.py +│   ├── settings.py +│   ├── urls.py +│   └── wsgi.py +└── manage.py + +1 directory, 9 files +``` + +To deploy on Heroku, we want to use `gunicorn` to run the `wsgi.py` file inside the project folder. This will do, in production, what `python manage.py runserver` does in development. We will declare this as a web process in the Procfile: + +``` +web: gunicorn django_demo.wsgi +``` +Be sure to replace `django_demo` with the name of your project folder. So, that will run the project, but there's one thing missing. You'll need to migrate your schema from your `models.py` to your new PostrgreSQL database. So, you will can a release command to the Procfile: + +``` +web: gunicorn django_demo.wsgi +release: python manage.py migrate +``` + +Be sure to use `python` here, not `py` or `python3`. This will migrate everything to your new database (create the tables for your schema). Note that you don't have to run `makemigrations` as well. That only needs to be done after you make changes to your Models, and all your migrations are already stored as Python files in your repo. +### Note: +If you have a different file structure (your `manage.py` and project folder are not in the root of the repo), you can still run these commands. Let's look at one of those file structures here: + +```console +$ tree +. +├── Pipfile +├── Pipfile.lock +└── django_demo + ├── django_demo + │   ├── __init__.py + │   ├── asgi.py + │   ├── settings.py + │   ├── urls.py + │   └── wsgi.py + └── manage.py + +2 directories, 8 files +``` + +So here `manage.py` **and** the project folder are both inside of another `django_demo` folder. Luckily, Procfile commands can still work: + +``` +web: cd django_demo && gunicorn django_demo.wsgi +release: cd django_demo && python manage.py migrate +``` + +The `&&` can be used to run a series of commands in one line. So each command will `cd` into `django_demo` before running the web process. + +## Push to Heroku + +The moment of truth. With git, add and commit the changes you've made locally. You can still push to GitHub (origin), but you can also push to a new remote: heroku. +```console +$ git push heroku main +``` +This will push your branch to main. If you are on another branch, you can use an altered command to push that branch *as* main: [https://devcenter.heroku.com/articles/git#deploying-from-a-branch-besides-main](https://devcenter.heroku.com/articles/git#deploying-from-a-branch-besides-main) + +When this command runs it will do a few things: +1. Push to the heroku remote +2. Look for a Pipfile or requirements.txt to recognize a Python app. +3. Use that file to install all project dependencies. +4. Run your web command. +5. Run your release command. + +If you see your migrations being made in the feedback, you're in pretty good shape. If things go at least 50% right, you should see something about your app being deployed to Heroku. It will be at `https://your-app-name.herokuapp.com`. So go there and see if it *really* worked. + +## Troubleshooting + +There's a decent chance that something went wrong. Let's start with the feedback that Heroku provides if there are errors. +1. The output after running `git push heroku main` +You may see an error in this output. Errors here usually occur somewhere in the process of building up your app. +2. If your app *is* deployed but you see "Application Error" when you visit the page, follow Heroku's advice and run `heroku logs` in your terminal to get an output of what happened. These errors are usually problems in the Procfile commands. + +### Common problems and how to solve them: +* No buildpack detected: Heroku could not tell that your project was written in Python. Be sure that your requirements.txt or Pipfile is at the root of the repo. +* Module not found: Usually this means there is more than one buildpack file detected, and Heroku read the one you weren't using. If you're going the requirements.txt route and still have a Pipfile and/or Pipfile.lock, delete those files. +* No web processes running. You'll see this after an apparently successful deployment, in the `heroku logs` feedback. Make sure that your Procfile commands are doing what you think they are. Double check the paths from the route of your repo to your `manage.py` and `wsgi.py` files. + +## Running `manage.py` commands +One thing to note is that you have a brand new Postr\greSQL database with empty tables and no data. That means no users or superusers. To create a superuser and use the admin panel on your deployed app, run this command: +```console +$ heroku run python manage.py createsuperuser +``` +Follow the prompts to create a superuser just as you would in local development. The `heroku run` prefix can be used to run any django command, including custom management commands you have written yourself. + +## Environment variables +To safely hide api keys and other important information that is not included in the repo, you can store them as environment variables (called config variables on Heroku). There are two ways to do this. +1. Heroku CLI +If you were to set your django SECRET_KEY to 12345 (not advised), you could run this command: +```console +$ heroku config:set SECRET_KEY=12345 +``` +2. Heroku Dashboard +This is a good time to introduce the Heroku Dashboard, where you can do much of the management of your app once it's been deployed. From the dashboard, click on your app, then click on settings >> reveal config vars. From here, you can set new key value pairs. + +Once you've set a config variable, you can access it anywhere in your code (likely your settings.py) with `os.environ.get('SECRET_KEY)`. This is also where the URL for your new PostgreSQL database is stored. + +## File uploads +If your Django application is something like a photo gallery, where the user uploads files through form submission, you will have to use an external file hosting service. [Cloudinary](https://cloudinary.com/documentation/django_integration) and [Dropbox](https://www.dropbox.com/developers/documentation/python#tutorial) both have SDKs (software development kits) for integrating with Python projects. \ No newline at end of file diff --git a/5 Capstone/apis_libraries.md b/5 Capstone/apis_libraries.md new file mode 100644 index 00000000..e61b0e72 --- /dev/null +++ b/5 Capstone/apis_libraries.md @@ -0,0 +1,12 @@ +# Apis and Libraries + +## Apis +There are tons of fun apis for Python! +[Here's](http://www.pythonforbeginners.com/api/list-of-python-apis) a few. +A lot of websites have their own apis, it's worth looking it up to see if any of your favorite sites have fun apis you can use! + +## Libraries +Wow there are so many awesome libraries for python you can integrate into your projects! +- [Here's](https://pythontips.com/2013/07/30/20-python-libraries-you-cant-live-without/) some. +- [Here's](https://tryolabs.com/blog/2017/12/19/top-10-python-libraries-of-2017/) some more. +- Oh dang! [Here's](http://www.pythonforbeginners.com/api/list-of-python-apis) even more! diff --git a/Code/Lee/Django/lab02/blog/__init__.py b/Code/Lee/Django/lab02/blog/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Lee/Django/lab02/blog/admin.py b/Code/Lee/Django/lab02/blog/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/Code/Lee/Django/lab02/blog/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Code/Lee/Django/lab02/blog/apps.py b/Code/Lee/Django/lab02/blog/apps.py new file mode 100644 index 00000000..79305878 --- /dev/null +++ b/Code/Lee/Django/lab02/blog/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + name = 'blog' diff --git a/Code/Lee/Django/lab02/blog/forms.py b/Code/Lee/Django/lab02/blog/forms.py new file mode 100644 index 00000000..7c283db4 --- /dev/null +++ b/Code/Lee/Django/lab02/blog/forms.py @@ -0,0 +1,17 @@ +from .models import BlogPost + + +from django import forms + + +class BlogForm(forms.ModelForm): + class Meta: + model = BlogPost + fields = [ # '__all__' # can do this if you want allllllll the form fields + 'title', + 'body', + ] + widgets = { + 'title' : forms.TextInput(attrs={'class':'form-control'}), + 'body' : forms.Textarea(attrs={'class':'form-control', 'rows':'4'}), + } \ No newline at end of file diff --git a/Code/Lee/Django/lab02/blog/migrations/0001_initial.py b/Code/Lee/Django/lab02/blog/migrations/0001_initial.py new file mode 100644 index 00000000..32d2e056 --- /dev/null +++ b/Code/Lee/Django/lab02/blog/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 2.2.24 on 2022-01-11 02:31 + +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='BlogPost', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('body', models.TextField(max_length=20000)), + ('public', models.BooleanField(default=True)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_edited', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/Code/Lee/Django/lab02/blog/migrations/__init__.py b/Code/Lee/Django/lab02/blog/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Lee/Django/lab02/blog/models.py b/Code/Lee/Django/lab02/blog/models.py new file mode 100644 index 00000000..56b2ea8b --- /dev/null +++ b/Code/Lee/Django/lab02/blog/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.contrib.auth import get_user_model +from users.models import User +# Create your models here. + +class BlogPost(models.Model): + title = models.CharField(max_length=200) + body = models.TextField(max_length=20000) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user') + public = models.BooleanField(default=True) + date_created = models.DateTimeField(auto_now_add=True) + date_edited = models.DateTimeField(auto_now=True) +# def save(self, args, kwargs) - Look it up on the django site \ No newline at end of file diff --git a/Code/Lee/Django/lab02/blog/tests.py b/Code/Lee/Django/lab02/blog/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Lee/Django/lab02/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Lee/Django/lab02/blog/urls.py b/Code/Lee/Django/lab02/blog/urls.py new file mode 100644 index 00000000..d61deaae --- /dev/null +++ b/Code/Lee/Django/lab02/blog/urls.py @@ -0,0 +1,13 @@ +from django.urls import path + +from . import views + +app_name = 'blog' + +urlpatterns = [ + path('', views.index, name='home'), + path('create/', views.create, name='create'), + path('/', views.profile, name='profile'), + + +] \ No newline at end of file diff --git a/Code/Lee/Django/lab02/blog/views.py b/Code/Lee/Django/lab02/blog/views.py new file mode 100644 index 00000000..0cbdf6ec --- /dev/null +++ b/Code/Lee/Django/lab02/blog/views.py @@ -0,0 +1,52 @@ +from django.shortcuts import ( + render, # helps render html templates + get_object_or_404, # returns the desired object if it exists or raises a 404 error + reverse, # lookup the path associated with a view name + redirect, +) +from django.http import HttpResponse, HttpResponseRedirect ,Http404 +from django.contrib.auth.decorators import login_required +from django.contrib.auth import ( + get_user_model, +) + +from .forms import BlogForm +from .models import BlogPost + +# Create your views here. +def index(request): + + return render(request, 'users/index.html') + + +def profile(request, username): + + user = get_object_or_404(get_user_model(), username=username) + + blogs = BlogPost.objects.all().filter(user=user).order_by('-date_created') + + context = { + 'user': user, + 'blogs': blogs + } + + return render(request, 'blog/profile.html', context) + +@login_required +def create(request): + if request.method == 'GET': + form = BlogForm + return render(request, 'blog/create.html', {'form':form}) + + elif request.method == 'POST': + form = BlogForm(request.POST) + + if form.is_valid(): + new_blog = form.save(commit=False) + + new_blog.user = request.user + + + new_blog.save() + print(new_blog) + return redirect(reverse('blog:profile', kwargs={'username':request.user.username})) \ No newline at end of file diff --git a/Code/Lee/Django/lab02/lab02/__init__.py b/Code/Lee/Django/lab02/lab02/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Lee/Django/lab02/lab02/settings.py b/Code/Lee/Django/lab02/lab02/settings.py new file mode 100644 index 00000000..c732471b --- /dev/null +++ b/Code/Lee/Django/lab02/lab02/settings.py @@ -0,0 +1,136 @@ +""" +Django settings for lab02 project. + +Generated by 'django-admin startproject' using Django 2.2.24. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.2/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__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '$2b-&5vm9($--j1c-)hx+$qyslo!tfhl4@mx-&m@a3qub@7sq5' + +# 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', + + # my apps + 'users', + 'blog', +] + +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 = 'lab02.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['templates'], + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'lab02.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.2/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.2/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.2/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.2/howto/static-files/ + +STATIC_URL = '/static/' + +# LTC - Adding project level static folder +STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] #[str(BASE_DIR.joinpath('static'))] + +# LTC - Add the CustomUser model as default user modeling +AUTH_USER_MODEL = 'users.User' + +# LTC - Not exactly sure why Keegan added this, but I'll do it to be safe... +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file diff --git a/Code/Lee/Django/lab02/lab02/urls.py b/Code/Lee/Django/lab02/lab02/urls.py new file mode 100644 index 00000000..f28811b8 --- /dev/null +++ b/Code/Lee/Django/lab02/lab02/urls.py @@ -0,0 +1,23 @@ +"""lab02 URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.2/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 + +urlpatterns = [ + path('admin/', admin.site.urls), + path('users/', include('users.urls')), + path('', include('blog.urls')), +] diff --git a/Code/Lee/Django/lab02/lab02/wsgi.py b/Code/Lee/Django/lab02/lab02/wsgi.py new file mode 100644 index 00000000..48249c44 --- /dev/null +++ b/Code/Lee/Django/lab02/lab02/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for lab02 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.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lab02.settings') + +application = get_wsgi_application() diff --git a/Code/Lee/Django/lab02/manage.py b/Code/Lee/Django/lab02/manage.py new file mode 100755 index 00000000..12db0e6b --- /dev/null +++ b/Code/Lee/Django/lab02/manage.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lab02.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) + + +if __name__ == '__main__': + main() diff --git a/Code/Lee/Django/lab02/static/index.css b/Code/Lee/Django/lab02/static/index.css new file mode 100644 index 00000000..e99d02da --- /dev/null +++ b/Code/Lee/Django/lab02/static/index.css @@ -0,0 +1 @@ +/* Use this to consolidate css files for apps */ \ No newline at end of file diff --git a/Code/Lee/Django/lab02/static/normalize.css b/Code/Lee/Django/lab02/static/normalize.css new file mode 100644 index 00000000..7aa705db --- /dev/null +++ b/Code/Lee/Django/lab02/static/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + + html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + } + + /* Sections + ========================================================================== */ + + /** + * Remove the margin in all browsers. + */ + + body { + margin: 0; + } + + /** + * Render the `main` element consistently in IE. + */ + + main { + display: block; + } + + /** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + + h1 { + font-size: 2em; + margin: 0.67em 0; + } + + /* Grouping content + ========================================================================== */ + + /** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + + hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ + } + + /** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + + pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ + } + + /* Text-level semantics + ========================================================================== */ + + /** + * Remove the gray background on active links in IE 10. + */ + + a { + background-color: transparent; + } + + /** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + + abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ + } + + /** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + + b, + strong { + font-weight: bolder; + } + + /** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + + code, + kbd, + samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ + } + + /** + * Add the correct font size in all browsers. + */ + + small { + font-size: 80%; + } + + /** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sub { + bottom: -0.25em; + } + + sup { + top: -0.5em; + } + + /* Embedded content + ========================================================================== */ + + /** + * Remove the border on images inside links in IE 10. + */ + + img { + border-style: none; + } + + /* Forms + ========================================================================== */ + + /** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + + button, + input, + optgroup, + select, + textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ + } + + /** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + + button, + input { /* 1 */ + overflow: visible; + } + + /** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + + button, + select { /* 1 */ + text-transform: none; + } + + /** + * Correct the inability to style clickable types in iOS and Safari. + */ + + button, + [type="button"], + [type="reset"], + [type="submit"] { + -webkit-appearance: button; + } + + /** + * Remove the inner border and padding in Firefox. + */ + + button::-moz-focus-inner, + [type="button"]::-moz-focus-inner, + [type="reset"]::-moz-focus-inner, + [type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; + } + + /** + * Restore the focus styles unset by the previous rule. + */ + + button:-moz-focusring, + [type="button"]:-moz-focusring, + [type="reset"]:-moz-focusring, + [type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; + } + + /** + * Correct the padding in Firefox. + */ + + fieldset { + padding: 0.35em 0.75em 0.625em; + } + + /** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + + legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ + } + + /** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + + progress { + vertical-align: baseline; + } + + /** + * Remove the default vertical scrollbar in IE 10+. + */ + + textarea { + overflow: auto; + } + + /** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + + [type="checkbox"], + [type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + } + + /** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + + [type="number"]::-webkit-inner-spin-button, + [type="number"]::-webkit-outer-spin-button { + height: auto; + } + + /** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + + [type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ + } + + /** + * Remove the inner padding in Chrome and Safari on macOS. + */ + + [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + } + + /** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + + ::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ + } + + /* Interactive + ========================================================================== */ + + /* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + + details { + display: block; + } + + /* + * Add the correct display in all browsers. + */ + + summary { + display: list-item; + } + + /* Misc + ========================================================================== */ + + /** + * Add the correct display in IE 10+. + */ + + template { + display: none; + } + + /** + * Add the correct display in IE 10. + */ + + [hidden] { + display: none; + } diff --git a/Code/Lee/Django/lab02/static/raven.png b/Code/Lee/Django/lab02/static/raven.png new file mode 100644 index 00000000..b995587b Binary files /dev/null and b/Code/Lee/Django/lab02/static/raven.png differ diff --git a/Code/Lee/Django/lab02/templates/base.html b/Code/Lee/Django/lab02/templates/base.html new file mode 100644 index 00000000..5ec315ee --- /dev/null +++ b/Code/Lee/Django/lab02/templates/base.html @@ -0,0 +1,28 @@ +{% load static %} + + + + + + + + + + + + + + + + Blog + + + + + {% include 'partials/_navbar.html' %} +
+ {% block content%} + {% endblock %} +
+ + \ No newline at end of file diff --git a/Code/Lee/Django/lab02/templates/blog/create.html b/Code/Lee/Django/lab02/templates/blog/create.html new file mode 100644 index 00000000..e3a39959 --- /dev/null +++ b/Code/Lee/Django/lab02/templates/blog/create.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} {% block content %} + +

New Entry

+ +
+
+
+ {% csrf_token %} + {{form}} + +
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/Code/Lee/Django/lab02/templates/blog/profile.html b/Code/Lee/Django/lab02/templates/blog/profile.html new file mode 100644 index 00000000..8a67c877 --- /dev/null +++ b/Code/Lee/Django/lab02/templates/blog/profile.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +

Welcome to {{ user.username }}'s Blog History

+
+ {% for blog in blogs %} +
+
{{ blog.date_created }}
+
+
{{ blog.title }}
+

{{ blog.body }}

+
+
+ {% endfor %} +
+{% endblock content %} + diff --git a/Code/Lee/Django/lab02/templates/partials/_navbar.html b/Code/Lee/Django/lab02/templates/partials/_navbar.html new file mode 100644 index 00000000..6a3126e5 --- /dev/null +++ b/Code/Lee/Django/lab02/templates/partials/_navbar.html @@ -0,0 +1,16 @@ +{%load static%} + \ No newline at end of file diff --git a/Code/Lee/Django/lab02/templates/users/index.html b/Code/Lee/Django/lab02/templates/users/index.html new file mode 100644 index 00000000..3bd5d32b --- /dev/null +++ b/Code/Lee/Django/lab02/templates/users/index.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} {% block content %} +

Class Raven's
Blog Home Page

+This should be a raven +{% endblock %} + diff --git a/Code/Lee/Django/lab02/templates/users/login.html b/Code/Lee/Django/lab02/templates/users/login.html new file mode 100644 index 00000000..5c8caed5 --- /dev/null +++ b/Code/Lee/Django/lab02/templates/users/login.html @@ -0,0 +1,25 @@ + + +{% extends 'base.html' %} + +{% block content %} + +{% if error %} +{{ error }} +{% endif %} + +
+
+ Log in + + + {{form.username}} + + {{form.password}} +
+ {% csrf_token %} + + +
+ +{% endblock content %} \ No newline at end of file diff --git a/Code/Lee/Django/lab02/templates/users/register.html b/Code/Lee/Django/lab02/templates/users/register.html new file mode 100644 index 00000000..894505fe --- /dev/null +++ b/Code/Lee/Django/lab02/templates/users/register.html @@ -0,0 +1,17 @@ +{% extends 'base.html' %} + +{% block content %} +

Register for the Blog

+ + +
+ + {{form}} + + {% csrf_token %} + + +
+ +{% endblock content %} + diff --git a/Code/Lee/Django/lab02/users/__init__.py b/Code/Lee/Django/lab02/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Lee/Django/lab02/users/admin.py b/Code/Lee/Django/lab02/users/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/Code/Lee/Django/lab02/users/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/Code/Lee/Django/lab02/users/apps.py b/Code/Lee/Django/lab02/users/apps.py new file mode 100644 index 00000000..4ce1fabc --- /dev/null +++ b/Code/Lee/Django/lab02/users/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = 'users' diff --git a/Code/Lee/Django/lab02/users/forms.py b/Code/Lee/Django/lab02/users/forms.py new file mode 100644 index 00000000..21f7174f --- /dev/null +++ b/Code/Lee/Django/lab02/users/forms.py @@ -0,0 +1,28 @@ +from django import forms +from .models import User + +class UserForm(forms.ModelForm): + + class Meta: + model = User + + fields = [ + 'first_name', + 'last_name', + 'email', + 'username', + 'password', + ] + + #Customize the HTML Class for each + widgets = { + 'first_name': forms.TextInput(attrs={'class':'form-control'}), + 'last_name': forms.TextInput(attrs={'class':'form-control'}), + 'email' : forms.TextInput(attrs={'class':'form-control'}), + 'username': forms.TextInput(attrs={'class':'form-control', 'id':'check-users-forms-py'}), + 'password': forms.PasswordInput(attrs={'class':'form-control'}), + } + +class UserAuthForm(UserForm): + class Meta(UserForm.Meta): + fields = ['username', 'password', ] \ No newline at end of file diff --git a/Code/Lee/Django/lab02/users/migrations/0001_initial.py b/Code/Lee/Django/lab02/users/migrations/0001_initial.py new file mode 100644 index 00000000..0467f5d5 --- /dev/null +++ b/Code/Lee/Django/lab02/users/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.24 on 2022-01-04 04:03 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0011_update_proxy_permissions'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/Code/Lee/Django/lab02/users/migrations/__init__.py b/Code/Lee/Django/lab02/users/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Lee/Django/lab02/users/models.py b/Code/Lee/Django/lab02/users/models.py new file mode 100644 index 00000000..df505777 --- /dev/null +++ b/Code/Lee/Django/lab02/users/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.contrib.auth.models import AbstractUser + +# Create your models here. +class User(AbstractUser): + + # add more User fields here + # Keegan did this, so I do this ¯\_(ツ)_/¯ + pass + + + def __str__(self): + return self.username \ No newline at end of file diff --git a/Code/Lee/Django/lab02/users/tests.py b/Code/Lee/Django/lab02/users/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Lee/Django/lab02/users/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Lee/Django/lab02/users/urls.py b/Code/Lee/Django/lab02/users/urls.py new file mode 100644 index 00000000..14170e37 --- /dev/null +++ b/Code/Lee/Django/lab02/users/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from . import views + +app_name='users' +urlpatterns = [ + path('register/', views.register, name='register'), + path('login/', views.login, name='login'), + path('logout', views.logout, name='logout'), +] \ No newline at end of file diff --git a/Code/Lee/Django/lab02/users/views.py b/Code/Lee/Django/lab02/users/views.py new file mode 100644 index 00000000..85c26dda --- /dev/null +++ b/Code/Lee/Django/lab02/users/views.py @@ -0,0 +1,75 @@ +from django.http.response import HttpResponse +from django.shortcuts import get_object_or_404, render, reverse, redirect +from django.http import HttpResponseRedirect +from django.contrib.auth import ( + authenticate, + login as user_login, + logout as user_logout, +) +from django.contrib.auth.decorators import login_required +from .forms import UserForm, UserAuthForm +from django.contrib.auth.models import User + + + + +# Create your views here. +# when register/ is visited with a GET request, the form will load. When register/ is visited with a POST request, the form will be processed +def register(request): + # if 'GET' Request, serve the blank form + if request.method == 'GET': + new_form = UserForm() + context = { 'form' : new_form } + return render(request, 'users/register.html', context) + + # if 'POST' Request, check values are valid and (if they are) create a new user with the info + elif request.method == 'POST': + form = UserAuthForm(request.POST) + + if not form.is_valid(): + + context = { + 'form' : UserAuthForm(), + 'error' : form.errors, + } + + elif form.is_valid(): + + add_user = form.save(commit=False) + + add_user.set_password(form.cleaned_data['password']) + + add_user.save() + + return HttpResponseRedirect(reverse('blog:home')) + + +def login(request): + # If the login request is 'GET', render the login page + if request.method == 'GET': + login_form = UserAuthForm() + return render(request, 'users/login.html', {'form':login_form}) + + # if it's a POST request, retrieve and send the login data to be authenticated in the DB + elif request.method == 'POST': + + username = request.POST['username'] + password = request.POST['password'] + authenticated_user = authenticate(request, username=username, password=password) + + # If invalid credentials, kick the user back to the login page with error message + if authenticated_user is None: + context = { + 'form': UserAuthForm(), + 'error': ['Username or password is incorrect! Please try again...'] + } + return render(request, 'users/login.html', context) + else: + username = authenticated_user.username + user_login(request, authenticated_user) + return HttpResponseRedirect(reverse('blog:home')) + + +def logout(request): + user_logout(request) + return redirect(reverse('users:login')) \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/manage.py b/Code/Philip/Django/Lab01_Todo/todo_project/manage.py new file mode 100755 index 00000000..1d0193af --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo_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) + + +if __name__ == '__main__': + main() diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/static/index.css b/Code/Philip/Django/Lab01_Todo/todo_project/static/index.css new file mode 100644 index 00000000..dfa2fb8a --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/static/index.css @@ -0,0 +1,62 @@ +@import url('https://fonts.googleapis.com/css2?family=Ubuntu:ital@1&display=swap'); + +header { + background: slategray; + padding: 30px 40px; + color: white; + text-align: center; +} + +h1 { + font-family: 'Ubuntu', sans-serif; + margin-bottom: 15px;; + text-align: center; +} + +ul li { + cursor: pointer; + position: relative; + padding: 4px 4px 4px 20px; + list-style-type: none; + background: rgba(105, 90, 205, 0.096); + font-size: 18px; + transition: 0.2s; + margin: 3%; + border-radius: 20px; + border-color:slateblue; + border-width:.2em; + border-style:solid; + font-family: 'Ubuntu', sans-serif; +} + +ul li:hover { + background: rgba(105, 90, 205, 0.288) +} + +.subtask { + background-color: rgba(137, 43, 226, 0.397); + border-color:rgba(137, 43, 226, 0.11) +} + +.completed { + text-decoration: line-through; + border-color:#555; + background: rgb(145, 163, 182); + color: #fff; +} + +.new-todo { + text-align: center; + font-size: xx-large; +} + +a:link, a:visited, a:hover{ + text-decoration:none; + color:#555; +} + +.fas:hover { + border-radius: 50%; + background-color: rgba(75, 109, 139, 0.445); + color: white; +} \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/templates/base.html b/Code/Philip/Django/Lab01_Todo/todo_project/templates/base.html new file mode 100644 index 00000000..d901b063 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/templates/base.html @@ -0,0 +1,11 @@ +{% load static%} + + + + + + + {% block content%} + {% endblock %} + + \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/templates/todo_app/todo_form.html b/Code/Philip/Django/Lab01_Todo/todo_project/templates/todo_app/todo_form.html new file mode 100644 index 00000000..16ad342e --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/templates/todo_app/todo_form.html @@ -0,0 +1,32 @@ +{% load static %} + + + + + + + New Honey Do + + + + + + +
+
+
+
+
+

New Honey Do


+
+ {% csrf_token%} + {{ form }} +

+ +
+
+
+
+
+
+ diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/__init__.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/admin.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/admin.py new file mode 100644 index 00000000..9ad62fb0 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin + +from .models import Todo +from .models import Priority + +@admin.register(Todo) +class TodoAdmin(admin.ModelAdmin): + list_display=['title','created_date','priority_choice','completed'] + +@admin.register(Priority) +class PriorityAdmin(admin.ModelAdmin): + list_display=['priority'] \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/apps.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/apps.py new file mode 100644 index 00000000..d8f1498d --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TodoAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'todo_app' diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0001_initial.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0001_initial.py new file mode 100644 index 00000000..a9709512 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.7 on 2021-12-30 02:11 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Priority', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('priority', models.CharField(choices=[('H', 'High'), ('M', 'Medium'), ('L', 'Low')], max_length=100)), + ], + ), + migrations.CreateModel( + name='Todo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=100)), + ('completed', models.BooleanField(default=False)), + ('completed_date', models.DateTimeField(blank=True, null=True)), + ('priority_choice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='todo_app.priority')), + ], + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0002_alter_priority_priority.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0002_alter_priority_priority.py new file mode 100644 index 00000000..1f28006d --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0002_alter_priority_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2021-12-30 03:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='priority', + name='priority', + field=models.CharField(max_length=7), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0003_todo_created_date.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0003_todo_created_date.py new file mode 100644 index 00000000..e3a89ed3 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0003_todo_created_date.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.7 on 2021-12-30 04:33 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0002_alter_priority_priority'), + ] + + operations = [ + migrations.AddField( + model_name='todo', + name='created_date', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0004_remove_priority_priority.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0004_remove_priority_priority.py new file mode 100644 index 00000000..77f83ef6 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0004_remove_priority_priority.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.7 on 2022-01-12 02:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0003_todo_created_date'), + ] + + operations = [ + migrations.RemoveField( + model_name='priority', + name='priority', + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0005_priority_priority.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0005_priority_priority.py new file mode 100644 index 00000000..7b18a403 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0005_priority_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2022-01-12 02:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0004_remove_priority_priority'), + ] + + operations = [ + migrations.AddField( + model_name='priority', + name='priority', + field=models.CharField(choices=[('LOW', 'low'), ('MEDIUM', 'medium'), ('HIGH', 'high')], default='low', max_length=7), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0006_auto_20220112_0302.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0006_auto_20220112_0302.py new file mode 100644 index 00000000..ff84158e --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0006_auto_20220112_0302.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.7 on 2022-01-12 03:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0005_priority_priority'), + ] + + operations = [ + migrations.AlterField( + model_name='priority', + name='priority', + field=models.CharField(choices=[('LOW', 'low'), ('MEDIUM', 'medium'), ('HIGH', 'high')], max_length=7), + ), + migrations.AlterField( + model_name='todo', + name='priority_choice', + field=models.ForeignKey(blank=True, default='low', null=True, on_delete=django.db.models.deletion.CASCADE, to='todo_app.priority'), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0007_alter_todo_priority_choice.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0007_alter_todo_priority_choice.py new file mode 100644 index 00000000..c174fb09 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0007_alter_todo_priority_choice.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.7 on 2022-01-12 03:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0006_auto_20220112_0302'), + ] + + operations = [ + migrations.AlterField( + model_name='todo', + name='priority_choice', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='todo_app.priority'), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0008_alter_priority_priority.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0008_alter_priority_priority.py new file mode 100644 index 00000000..f6988ac4 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0008_alter_priority_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2022-01-12 04:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0007_alter_todo_priority_choice'), + ] + + operations = [ + migrations.AlterField( + model_name='priority', + name='priority', + field=models.CharField(max_length=20), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0009_alter_priority_priority.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0009_alter_priority_priority.py new file mode 100644 index 00000000..6eee2308 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0009_alter_priority_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2022-01-12 04:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0008_alter_priority_priority'), + ] + + operations = [ + migrations.AlterField( + model_name='priority', + name='priority', + field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], default='low', max_length=20), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0010_alter_priority_priority.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0010_alter_priority_priority.py new file mode 100644 index 00000000..d5f9e7e1 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/0010_alter_priority_priority.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2022-01-12 04:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('todo_app', '0009_alter_priority_priority'), + ] + + operations = [ + migrations.AlterField( + model_name='priority', + name='priority', + field=models.CharField(choices=[('low', 'Low'), ('medium', 'Medium'), ('high', 'High')], max_length=20), + ), + ] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/__init__.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/models.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/models.py new file mode 100644 index 00000000..7ff99955 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/models.py @@ -0,0 +1,26 @@ +from django.db import models +from django.db.models.deletion import SET_DEFAULT +from django.db.models.fields import CharField +from django.db import models + + + +class Priority(models.Model): + priority= models.CharField(max_length=20) + + def __str__(self): + return self.priority + +priority, created = Priority.objects.get_or_create(priority='High') +priority, created = Priority.objects.get_or_create(priority='Medium') +priority, created = Priority.objects.get_or_create(priority='Low') + +class Todo(models.Model): + title = models.CharField(max_length=100) + created_date = models.DateTimeField(auto_now_add=True) + completed = models.BooleanField(default=False) + completed_date = models.DateTimeField(null=True, blank=True) + priority_choice = models.ForeignKey(Priority,on_delete=models.CASCADE, blank=True, null=True) + + def __str__(self): + return self.title + self.priority_choice \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/templates/index.html b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/templates/index.html new file mode 100644 index 00000000..b9b91aa5 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/templates/index.html @@ -0,0 +1,67 @@ +{% load static %} + + + + + + + + Honey Do List + + + + + + +
+
+
+
+
+
+

Honey Do List

+

Today is: {% now "jS F Y" %}

+
+ + +
+
+
+
+
+ +
+ +
+ +
+
+ +
    + + {% for each_todo in items%} + +
  • {{each_todo.title}} + +
      +
    • Priority: {{each_todo.priority_choice}}
    • +
    • Created: {{each_todo.created_date}}
    • +
    +
  • + {% endfor %} +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/templates/todo_detail.html b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/templates/todo_detail.html new file mode 100644 index 00000000..f60e18bc --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/templates/todo_detail.html @@ -0,0 +1,35 @@ +{% load static %} + + + + + + + New Honey Do + + + + + + + + + + + diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/tests.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/views.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/views.py new file mode 100644 index 00000000..ef5037d0 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_app/views.py @@ -0,0 +1,63 @@ +from django.shortcuts import render + +from datetime import datetime +from django.views.generic import CreateView, ListView + +from .models import Todo + +#Using class based views +#List View for main page +class TodoListView(ListView): + model = Todo + context_object_name = "items" + template_name = "index.html" + +#Create item view +class TodoCreateView(CreateView): + model = Todo + fields = ['title','priority_choice'] + success_url = '/index' + + + + + + + + + +#Function based views +'''def index(request): + all_todos = Todo.objects.all() + return render(request, 'index.html',{ + 'all_todos':all_todos, + }) + +def details(request, item_id): + each_todo = Todo.objects.get(id=item_id) + return render(request, 'save_todo_item.html', { + 'each_todo':each_todo, + }) + +def save_todo_item(request,item_id): + new_todo = Todo.objects.create() + +def todo_detail(request, item_id): + item = Todo.objects.get(id=item_id) + return render(request, 'todo_detail.html', {'item':item,}) + +class TodoCreateView(CreateView): + model = Todo + fields = ['title','priority_choice'] + success_url = '/index' + +class TodoUpdateView(UpdateView): + model = Todo + fields = ['title','priority_choice','complete','completed_date'] + success_url = '/index' + +class TodoDetailView(DetailView): + model = Todo + context_object_name = 'todo_detail' + template_name = 'todo_detail.html' + ''' \ No newline at end of file diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/__init__.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/asgi.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/asgi.py new file mode 100644 index 00000000..e793c4bc --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for todo_project project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo_project.settings') + +application = get_asgi_application() diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/settings.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/settings.py new file mode 100644 index 00000000..a4d0ca5f --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/settings.py @@ -0,0 +1,133 @@ +""" +Django settings for todo_project project. + +Generated by 'django-admin startproject' using Django 3.2.7. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-0x+1)7@!sce-o_aq^o%&220z!$_17s0*i=pk5-bj3vi7cbi=hc' + +# 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', + + 'todo_app', +] + +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 = 'todo_project.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['templates', + BASE_DIR / 'static', + ], + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'todo_project.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/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/3.2/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/3.2/howto/static-files/ + +STATIC_URL = '/static/' + #helps to lead Django to our static url +STATICFILES_DIRS = [ + BASE_DIR / 'static', +] + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/urls.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/urls.py new file mode 100644 index 00000000..3eb29953 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/urls.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from django.urls import path + +from todo_app import views + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', views.TodoListView.as_view(), name='home.list'), + #path('/', views.TodoDetailView.as_view(), name='todo_detail'), + path('index/', views.TodoListView.as_view(), name='index.list'), + path('todo_form/', views.TodoCreateView.as_view(), name='todo_form.new'), + # path('details/', views.TodoDetailView.as_view(), name='todo_form.details') + +] diff --git a/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/wsgi.py b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/wsgi.py new file mode 100644 index 00000000..5c559837 --- /dev/null +++ b/Code/Philip/Django/Lab01_Todo/todo_project/todo_project/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for todo_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/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo_project.settings') + +application = get_wsgi_application() diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit/__init__.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit/asgi.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit/asgi.py new file mode 100644 index 00000000..f13674b1 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for blogit project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blogit.settings') + +application = get_asgi_application() diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit/settings.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit/settings.py new file mode 100644 index 00000000..cba8bce0 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit/settings.py @@ -0,0 +1,126 @@ +""" +Django settings for blogit project. + +Generated by 'django-admin startproject' using Django 4.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-++4wythj)-65ja(uk30a!^3qlkc3u!k3zaet(*mz-aj5i0t4ql' + +# 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', + 'blogit_app', +] + +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 = 'blogit.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [str(BASE_DIR.joinpath('templates'))], + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'blogit.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/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/4.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = 'static/' +STATICFILES_DIRS = [str(BASE_DIR.joinpath('static'))] + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +LOGIN_REDIRECT_URL = 'profile' diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit/urls.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit/urls.py new file mode 100644 index 00000000..e4e2a2a6 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit/urls.py @@ -0,0 +1,22 @@ +"""blogit URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.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 + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('blogit_app.urls')), +] diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit/wsgi.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit/wsgi.py new file mode 100644 index 00000000..498de725 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for blogit 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/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blogit.settings') + +application = get_wsgi_application() diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/__init__.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/admin.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/admin.py new file mode 100644 index 00000000..47f03fdd --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from .models import Post + +admin.site.register(Post) diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/apps.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/apps.py new file mode 100644 index 00000000..93fe12c7 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlogitAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'blogit_app' diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/forms.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/forms.py new file mode 100644 index 00000000..f1cdedac --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/forms.py @@ -0,0 +1,28 @@ +from django import forms +from .models import Post + +class PostForm(forms.ModelForm): + class Meta: + model = Post + fields = ('title', 'title_tag', 'body', 'public') + + widgets = { + 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'give your thoughts a title here'}), + 'title_tag': forms.TextInput(attrs={'class': 'form-control', 'placeholder':'tell us what its about'}), + 'user': forms.Select(attrs={'class': 'form-control'}), + 'body': forms.Textarea(attrs={'class': 'form-control', 'placeholder':'go ahead, get creative'}), + 'public': forms.NullBooleanSelect(attrs={'class': 'form-control', 'placeholder':'would you like the world to know?'}), + } + +class EditForm(forms.ModelForm): + class Meta: + model = Post + fields = ('title', 'title_tag', 'body', 'public') + + widgets = { + 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'give your thoughts a title here'}), + 'title_tag': forms.TextInput(attrs={'class': 'form-control', 'placeholder':'tell us what its about'}), + #'author': forms.Select(attrs={'class': 'form-control'}), + 'body': forms.Textarea(attrs={'class': 'form-control', 'placeholder':'go ahead, get creative'}), + 'public': forms.NullBooleanSelect(attrs={'class': 'form-control', 'placeholder':'would you like the world to know?'}), + } \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/0001_initial.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/0001_initial.py new file mode 100644 index 00000000..ba90b675 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.7 on 2022-01-13 01:34 + +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='Post', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('title_tag', models.CharField(max_length=255)), + ('body', models.TextField()), + ('public', models.BooleanField(default=True)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_edited', models.DateTimeField(auto_now=True)), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/0002_rename_author_post_user.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/0002_rename_author_post_user.py new file mode 100644 index 00000000..6d459374 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/0002_rename_author_post_user.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2022-01-13 02:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blogit_app', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='post', + old_name='author', + new_name='user', + ), + ] diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/__init__.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/models.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/models.py new file mode 100644 index 00000000..ed8458fc --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/models.py @@ -0,0 +1,21 @@ +from django.db import models +from django.contrib.auth.models import User +from django.contrib.auth import get_user_model +from django.urls import reverse +from datetime import datetime, date + +class Post(models.Model): + title = models.CharField(max_length=255) + title_tag = models.CharField(max_length=255) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notes') + body = models.TextField() + public = models.BooleanField(default=True) + date_created = models.DateTimeField(auto_now_add=True) + date_edited = models.DateTimeField(auto_now=True, blank=True) + + + def __str__(self): + return self.title + ' by ' + str(self.user) + + def get_absolute_url(self): + return reverse('profile') \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/tests.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/urls.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/urls.py new file mode 100644 index 00000000..5de1abc0 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/urls.py @@ -0,0 +1,17 @@ + +from django.urls import path +from . import views +from .views import ArticleDetailView, AddPostView, UpdatePostView, DeletePostView, LoginInterfaceView, LogoutInterfaceView, SignupView, HomeView + +urlpatterns = [ + + path('', HomeView.as_view(), name="home"), + path('profile',HomeView.as_view(), name="profile"), + path('post/', ArticleDetailView.as_view(), name='post_detail'), + path('add_post/', AddPostView.as_view(), name='add_post'), + path('post//edit', UpdatePostView.as_view(), name='update_post'), + path('post//delete', DeletePostView.as_view(), name='delete_post'), + path('login', LoginInterfaceView.as_view(), name='login'), + path('logout', LogoutInterfaceView.as_view(), name='logout'), + path('register', SignupView.as_view(), name='register'), + ] diff --git a/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/views.py b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/views.py new file mode 100644 index 00000000..7d7ba4e6 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/blogit_app/views.py @@ -0,0 +1,77 @@ +"""Django Lab02 Blog Post +By: Philip Bartoo +1/11/2022 + +Build a blog using Django. + +I used almost all Class based views as this is much quicker to set up CRUD operations than working through function based views. +The primary thing I learned was the need to build an app along a template first, then to customize later. +This should allow me to develop apps faster. I also added a url.py file to the app, which simplified template access that I struggled with in my first Django app. The other big learning point was setup of +the login function. In this project the login is built into the app. Alternatively the login could be built as its own app. I didn't add +a static folder to this app in the interest of time, but will plan to go back and update at a later point in time. There are several +relatively simple functional improvements which need to be made, such as only showing the logout function when logged in. I also did not +include a list of all posts, another item I'd really like to update in future revisions. """ + +from django.contrib.auth.forms import UserCreationForm +from django.shortcuts import render +from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView +from .models import Post, User +from .forms import PostForm, EditForm +from django.urls import reverse_lazy +from django.contrib.auth.mixins import LoginRequiredMixin +from django.http.response import HttpResponseRedirect +from django.contrib.auth.views import LoginView, LogoutView + + + +class LoginInterfaceView(LoginView): + template_name = 'login.html' + +class LogoutInterfaceView(LogoutView): + template_name = 'logout.html' + +class SignupView(CreateView): + form_class = UserCreationForm + template_name = 'register.html' + success_url = reverse_lazy('profile') + +#def home(request): + #posts=Post.objects.order_by('-date_created') + #return render(request,'home.html', {'posts':posts}) + +class HomeView(LoginRequiredMixin, ListView): + model = Post + template_name = 'home.html' + ordering = ['-date_created'] + login_url = "login" + + def get_queryset(self): + return self.request.user.notes.all() + +class ArticleDetailView(DetailView): + model = Post + template_name = 'post_details.html' + +class AddPostView(CreateView): + model = Post + form_class = PostForm + template_name = 'add_post.html' + #fields = '__all__' + #fields = ('title', 'title_tag','body','public') + + def form_valid(self, form): + self.object = form.save(commit=False) + self.object.user = self.request.user + self.object.save() + return HttpResponseRedirect(self.get_success_url()) + +class UpdatePostView(UpdateView): + model = Post + form_class = EditForm + template_name = 'update_post.html' + #fields = ['title', 'title_tag', 'body', 'public',] + +class DeletePostView(DeleteView): + model = Post + template_name = 'delete_post.html' + success_url = reverse_lazy('profile') \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/manage.py b/Code/Philip/Django/Lab02_Blog/blogit/manage.py new file mode 100755 index 00000000..9b3c20d6 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blogit.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) + + +if __name__ == '__main__': + main() diff --git a/Code/Philip/Django/Lab02_Blog/blogit/static/styles.css b/Code/Philip/Django/Lab02_Blog/blogit/static/styles.css new file mode 100644 index 00000000..f76dd28b --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/static/styles.css @@ -0,0 +1,81 @@ +@import url('https://fonts.googleapis.com/css2?family=Rock+Salt&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@1,300&family=Sora:wght@100&display=swap'); + +.flex-container { + display: flex; + flex-flow: row wrap; + justify-content: space-evenly; + padding: 0; + margin: 0; + list-style: none; + background-image: linear-gradient(to right, slateblue, #f28500); + } + .flex-item { + padding: 5px; + margin: 0px; + width: auto; + height: 50px; + line-height: 2.2em; + color: whitesmoke; + font-size: 1.5em; + text-align: center; + font-family: 'Sora', sans-serif; + } + .heading { + font-family: 'Rock Salt', cursive; + font-size:xx-large; + text-align: center; + font-size: 3em; + } + .blog-article { + background-color: #f2f1f8; + margin: 5%; + border-style: solid; + border-color:#f28500; + border-width: 1px; + padding: 30px; + border-radius: 30px; + list-style-type: none; + + } + .blog-entry { + display:flex; + color: #f28500; + font-size: 2em; + font-weight:bolder; + font-family: 'Montserrat', sans-serif; + text-align:center; + } + + + .paragraph { + color: #333333; + font-family: 'Sora', sans-serif; + font-size: 1.2em; + line-height: 1.4em; + letter-spacing: 0.03em; + } + + .item { + list-style-type:none; + } + .date { + font-family: 'Sora', sans-serif; + color: slateblue; + } + .details { + border-radius: 100px; + text-align:center; + border-style: solid; + border-color:slateblue; + border-width: 1px; + margin: 50px; + padding: 0px; + + + } + .authors-info { + font-family: 'Sora', sans-serif; + color: slateblue; + + } \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/add_post.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/add_post.html new file mode 100644 index 00000000..d672b52c --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/add_post.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% block title %}Create a new blog post{% endblock %} +{% block content %} + +

Add Post

+
+
+
+
+
+ {% csrf_token %} + {{ form.as_p }} + Back +
+
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/base.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/base.html new file mode 100644 index 00000000..4f4b38a8 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/base.html @@ -0,0 +1,55 @@ +{% load static %} + + + + + + + + + + + {% block title%}My Blog{% endblock %} + + + +
+
+ + {% block content %} + + {% endblock %} +
+ + + + + + + + + + \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/delete_post.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/delete_post.html new file mode 100644 index 00000000..5e86177f --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/delete_post.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% block title %}Delete blog post{% endblock %} +{% block content %} + +

Delete Post

+

Delete: {{ post.title }}

+
+
+
+ {% csrf_token %} + Are you sure?
+ Back +
+ + +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/home.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/home.html new file mode 100644 index 00000000..15d6d8ca --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/home.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} +{% load static %} +{% block content %} +
+ +

Welcome to BlogIt!

+ +
+
+

Profile Page

+

{{ user.first_name }} {{ user.last_name }}

+
+ +
    +{% for post in object_list %} +
  • + {{ post.title }} + +
      +
    • Created: {{ post.date_created }} | + Edit | + Delete + +
    • +
    +
  • +{% endfor %} +
+ +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/login.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/login.html new file mode 100644 index 00000000..2ef6412a --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/login.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% block title %}Welcome to BlogIt{% endblock %} +{% block content %} + +

Welcome to BlogIt

+

+

Login

+
+ + {% csrf_token %} + {{ form.as_p }} + + +
+

+Register + + +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/logout.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/logout.html new file mode 100644 index 00000000..245f6d91 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/logout.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% block title %}Logout{% endblock %} +{% block content %} +

+

Thanks for Stopping By!

+ + +Login Page + + +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/post_details.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/post_details.html new file mode 100644 index 00000000..3a18e043 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/post_details.html @@ -0,0 +1,19 @@ +{% extends 'base.html' %} +{% block title %}{{ post.title_tag }}{% endblock %} +{% block content %} + +

{{ post.title }}

+

{{ post.author.first_name }} {{ post.author.last_name }} Created: {{ post.date_created }} | + Edit | + Delete + + +


+
+
+

{{ post.body }}

+ +

+Back + +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/register.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/register.html new file mode 100644 index 00000000..fd441b0c --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/register.html @@ -0,0 +1,16 @@ +{% extends 'base.html' %} +{% block title %}Register{% endblock %} +{% block content %} + +

Register

+

+
+
+ {% csrf_token %} + {{ form.as_p }} + +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/Code/Philip/Django/Lab02_Blog/blogit/templates/update_post.html b/Code/Philip/Django/Lab02_Blog/blogit/templates/update_post.html new file mode 100644 index 00000000..53d3e5d2 --- /dev/null +++ b/Code/Philip/Django/Lab02_Blog/blogit/templates/update_post.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% block title %}Edit blog post{% endblock %} +{% block content %} + +

Update Post

+
+
+
+
+
+ {% csrf_token %} + {{ form.as_p }} + Back +
+
+
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/blog_project/__init__.py b/Code/Rodney/Django/Blog/blog_project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Blog/blog_project/asgi.py b/Code/Rodney/Django/Blog/blog_project/asgi.py new file mode 100644 index 00000000..f99cd99a --- /dev/null +++ b/Code/Rodney/Django/Blog/blog_project/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for blog_project project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_project.settings') + +application = get_asgi_application() diff --git a/Code/Rodney/Django/Blog/blog_project/settings.py b/Code/Rodney/Django/Blog/blog_project/settings.py new file mode 100644 index 00000000..bd8b3e4b --- /dev/null +++ b/Code/Rodney/Django/Blog/blog_project/settings.py @@ -0,0 +1,134 @@ +""" +Django settings for blog_project project. + +Generated by 'django-admin startproject' using Django 4.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-!fgg8653)#d)=9-xuon)z_r-d56dfae0ljy3*k3(-5o_lcqro2' + +# 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', + 'users_app', + 'post_app', + +] + +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 = 'blog_project.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [str(BASE_DIR.joinpath('templates'))], + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'blog_project.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/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/4.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'America/Detroit' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = 'static/' + +STATICFILES_DIRS = [str(BASE_DIR.joinpath('static'))] + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +AUTH_USER_MODEL = 'users_app.User' + +## where user goes if not logged in + +LOGIN_URL = '/users/login' \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/blog_project/urls.py b/Code/Rodney/Django/Blog/blog_project/urls.py new file mode 100644 index 00000000..eaa97d7f --- /dev/null +++ b/Code/Rodney/Django/Blog/blog_project/urls.py @@ -0,0 +1,23 @@ +"""blog_project URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.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 + +urlpatterns = [ + path('admin/', admin.site.urls), + path('users/', include('users_app.urls')), + path('', include('post_app.urls')), +] diff --git a/Code/Rodney/Django/Blog/blog_project/wsgi.py b/Code/Rodney/Django/Blog/blog_project/wsgi.py new file mode 100644 index 00000000..97b2fc7c --- /dev/null +++ b/Code/Rodney/Django/Blog/blog_project/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for blog_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/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_project.settings') + +application = get_wsgi_application() diff --git a/Code/Rodney/Django/Blog/manage.py b/Code/Rodney/Django/Blog/manage.py new file mode 100755 index 00000000..e04dc851 --- /dev/null +++ b/Code/Rodney/Django/Blog/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_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) + + +if __name__ == '__main__': + main() diff --git a/Code/Rodney/Django/Blog/post_app/__init__.py b/Code/Rodney/Django/Blog/post_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Blog/post_app/admin.py b/Code/Rodney/Django/Blog/post_app/admin.py new file mode 100644 index 00000000..356c101a --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin + +from .models import BlogPost + + +admin.site.register(BlogPost) \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/post_app/apps.py b/Code/Rodney/Django/Blog/post_app/apps.py new file mode 100644 index 00000000..d4251664 --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PostAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'post_app' diff --git a/Code/Rodney/Django/Blog/post_app/forms.py b/Code/Rodney/Django/Blog/post_app/forms.py new file mode 100644 index 00000000..12e54891 --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/forms.py @@ -0,0 +1,24 @@ +from django import forms +from django.db.models import fields +from .models import BlogPost, User + + + +class BlogPostForm(forms.ModelForm): + + class Meta: + + model = BlogPost + + fields = [ + 'blog_title', + 'blog_body', + 'public_post' + ] + + widgets = { + 'blog_title': forms.TextInput(attrs={'class':'form-control'}), + 'blog_body': forms.Textarea(attrs={'class':'form-control'}), + + } + diff --git a/Code/Rodney/Django/Blog/post_app/migrations/0001_initial.py b/Code/Rodney/Django/Blog/post_app/migrations/0001_initial.py new file mode 100644 index 00000000..9ddbaa24 --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0.1 on 2022-01-09 17:36 + +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='BlogPost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('blog_title', models.CharField(max_length=200)), + ('blog_body', models.TextField(max_length=5000)), + ('public_post', models.BooleanField()), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('edited_date', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blogpost', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/Code/Rodney/Django/Blog/post_app/migrations/__init__.py b/Code/Rodney/Django/Blog/post_app/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Blog/post_app/models.py b/Code/Rodney/Django/Blog/post_app/models.py new file mode 100644 index 00000000..604970ee --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/models.py @@ -0,0 +1,19 @@ +from django.db import models + +from django.contrib.auth.models import AbstractUser +from django.contrib.auth import get_user_model +from django.db.models.fields import BooleanField, TextField + +from users_app.models import User + + +class BlogPost(models.Model): + blog_title = models.CharField(max_length=200) + blog_body = models.TextField(max_length=5000) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blogpost') + public_post = models.BooleanField() + created_date = models.DateTimeField(auto_now_add=True) + edited_date = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.user.username diff --git a/Code/Rodney/Django/Blog/post_app/tests.py b/Code/Rodney/Django/Blog/post_app/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Rodney/Django/Blog/post_app/urls.py b/Code/Rodney/Django/Blog/post_app/urls.py new file mode 100644 index 00000000..8feeee98 --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from django.urls.resolvers import URLPattern + +from . import views + + +app_name = 'post_app' + +urlpatterns = [ + path('', views.home, name='home'), + path('create/', views.create, name='create'), + path('delete//', views.delete, name='delete'), +] + diff --git a/Code/Rodney/Django/Blog/post_app/views.py b/Code/Rodney/Django/Blog/post_app/views.py new file mode 100644 index 00000000..9cd5838c --- /dev/null +++ b/Code/Rodney/Django/Blog/post_app/views.py @@ -0,0 +1,62 @@ +from django.contrib import auth +from django.shortcuts import get_object_or_404, render, redirect +from django.urls import reverse +from django.http import HttpResponse + +from django.contrib.auth import (authenticate, get_user_model, +login as django_login, +logout as django_logout ) +from django.contrib.auth.decorators import login_required +from.forms import BlogPostForm +from .models import BlogPost, User + + + +def home(request): + public_blog_posts = BlogPost.objects.filter(public_post=True).order_by('-created_date') + + context = { + 'blogposts': public_blog_posts + } + + # for blogpost in public_blog_posts: + + # print(blogpost.blog_title) + + return render(request, 'post/home.html', context) + + +@login_required +def create(request): + + if request.method == "GET": + + form = BlogPostForm() + context = { + 'form': form + } + + return render(request, 'post/create.html', context) + + elif request.method == "POST": + + form = BlogPostForm(request.POST) + + if form.is_valid(): + + new_post = form.save(commit = False) + + ## connect the new post with the user (foreign key) + new_post.user = request.user + + new_post.save() + + return redirect(reverse('post_app:home')) + + +@login_required +def delete(request, pk): + deleted_item = get_object_or_404(BlogPost, pk=pk) + deleted_item.delete() + + return redirect(reverse('post_app:home')) diff --git a/Code/Rodney/Django/Blog/static/css/index.css b/Code/Rodney/Django/Blog/static/css/index.css new file mode 100644 index 00000000..365dd0d1 --- /dev/null +++ b/Code/Rodney/Django/Blog/static/css/index.css @@ -0,0 +1,68 @@ + +* { + font-family: Verdana, Geneva, Tahoma, sans-serif; + font-size: 1.1rem; +} + +#profile-user { + + font-size: 3rem; +} + + +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#main-title { +margin-top: 20px; +font-size: 1.5rem; +color: rgb(205, 240, 206) +} + + +.fas { + font-size: 25px; +} + +.far { + font-size: 25px; +} + + +.user-registration { + color: white; + padding: 30px; + margin: 30px; + border-radius: 30px; + background-color: rgb(25, 135, 84); +} + +#id_username { + font-size: 1.2rem; + margin-bottom: 20px; +} + +.register-button { + margin-top: 20px; +} + +.profile-margin { + margin-top: 50px; +} + +.blogbody { + border-width: 20px; + border: black; + border: solid; + + +} + +label { + color: rgb(205, 240, 206); + margin: 5px; +} diff --git a/Code/Rodney/Django/Blog/templates/base.html b/Code/Rodney/Django/Blog/templates/base.html new file mode 100644 index 00000000..7cb1e0c8 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/base.html @@ -0,0 +1,96 @@ +{% load static %} + + + + + + + + + + + + BlogApp + + + + + + + + + +
+ + {% if errors %} + + {% for error in errors %} + + + + + {% endfor %} + {% endif %} + + + + {% block content %} + + + + {% endblock content %} + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/templates/post/_card-titles.html b/Code/Rodney/Django/Blog/templates/post/_card-titles.html new file mode 100644 index 00000000..5066a812 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/post/_card-titles.html @@ -0,0 +1,42 @@ + + +{% block content %} +{% load static %} + + + + +
+ +
+
+ {{ blogpost.blog_title }} Created by: You +
+ +
+ + + Posted: {{ blogpost.created_date|date }} + + + {% if blogpost.user == request.user %} + + + + {% endif %} + + + +
+ +
+ +
+
+ + +
+ + +{% endblock content %} + diff --git a/Code/Rodney/Django/Blog/templates/post/_card.html b/Code/Rodney/Django/Blog/templates/post/_card.html new file mode 100644 index 00000000..6d8c48ed --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/post/_card.html @@ -0,0 +1,47 @@ + + +{% block content %} + {% load static %} + + + + +
+ +
+
+ {{ blogpost.blog_title }} Created by: {{ blogpost.user }} +
+ +
+ {{ blogpost.blog_body }} + +
+ +
+ + + Posted: {{ blogpost.created_date|date }} + + + {% if blogpost.user == request.user %} + + + + {% endif %} + + + +
+ +
+ +
+
+ + +
+ + +{% endblock content %} + diff --git a/Code/Rodney/Django/Blog/templates/post/create.html b/Code/Rodney/Django/Blog/templates/post/create.html new file mode 100644 index 00000000..fff759c2 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/post/create.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} + +{% load static %} + + + +{% block content %} + + +

New blog post

+ + +
+
+ + {% csrf_token %} + {{form}} + + +
+ + +
+ + + + + + + + +{% endblock content %} \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/templates/post/home.html b/Code/Rodney/Django/Blog/templates/post/home.html new file mode 100644 index 00000000..febb2768 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/post/home.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% load static %} + + +{% block content %} + + + +
+ +

+ Blog Posts +

+ + {% for blogpost in blogposts %} + + {% include 'post/_card.html' with blogposts=blogposts %} + + + {% endfor %} + + +
+ + +{% endblock content %} \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/templates/users/login.html b/Code/Rodney/Django/Blog/templates/users/login.html new file mode 100644 index 00000000..87047ce5 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/users/login.html @@ -0,0 +1,47 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block content %} + + +

+ + MyBlogz Log-in + +

+ +
+ +
+ + + {{form.username}} + +
+ +
+ + + {{form.password}} + +
+ + {% csrf_token %} + + + +
+ + + + + + + + + + + + +{% endblock content %} diff --git a/Code/Rodney/Django/Blog/templates/users/register.html b/Code/Rodney/Django/Blog/templates/users/register.html new file mode 100644 index 00000000..6010a2a1 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/users/register.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} + +{% load static %} + +{% block content %} + + +

+ + MyBlogz Registration +

+ +
+ + +
+ + + {{form.username}} + +
+ +
+ + + {{form.password}} + +
+ + {% csrf_token %} + + +
+ + + + + +{% endblock content %} diff --git a/Code/Rodney/Django/Blog/templates/users/userprofile.html b/Code/Rodney/Django/Blog/templates/users/userprofile.html new file mode 100644 index 00000000..6c9c8009 --- /dev/null +++ b/Code/Rodney/Django/Blog/templates/users/userprofile.html @@ -0,0 +1,73 @@ + + +{% extends 'base.html' %} {% load static %} {% block content %} + + +

+ Welcome {{user.username}} ! +

+ + +
+ +
+ + + + + + + + + + + + + + + + + +
Username:{{user.username}}
Profile created:{{user.date_joined|date}}
+ +
+ + +
+ + + + + +
+ +

+ {{user.username}}'s blog posts +

+ + {% for blogpost in user.blogpost.all %} + + {% include 'post/_card-titles.html' with blogpost=blogpost %} + + + {% endfor %} + + +
+ + + + + + + + + + + + + + + + +{% endblock content %} diff --git a/Code/Rodney/Django/Blog/users_app/__init__.py b/Code/Rodney/Django/Blog/users_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Blog/users_app/admin.py b/Code/Rodney/Django/Blog/users_app/admin.py new file mode 100644 index 00000000..6d53b53a --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin + +from .models import User + +admin.site.register(User, UserAdmin) diff --git a/Code/Rodney/Django/Blog/users_app/apps.py b/Code/Rodney/Django/Blog/users_app/apps.py new file mode 100644 index 00000000..7200dd57 --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsersAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'users_app' diff --git a/Code/Rodney/Django/Blog/users_app/forms.py b/Code/Rodney/Django/Blog/users_app/forms.py new file mode 100644 index 00000000..9d645eb5 --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/forms.py @@ -0,0 +1,33 @@ +from django import forms +from django.db.models import fields +from .models import User + +class UserForm(forms.ModelForm): + + #meta describes data used to make form, which fields, widgets, database model + + class Meta: + + #this form is for the User model + model = User + + fields = [ + 'first_name', + 'last_name', + 'user_blog_name', + ] + + widgets = { + 'first_name': forms.TextInput(attrs={'class':'form-control'}), + 'last_name': forms.TextInput(attrs={'class':'form-control'}), + 'user_blog_name': forms.TextInput(attrs={'class':'form-control'}), + 'username': forms.TextInput(attrs={'class':'form-control'}), + 'password': forms.PasswordInput(attrs={'class':'form-control'}), + } + + +class UserAuthForm(UserForm): + class Meta(UserForm.Meta): + fields = ['username', 'password'] + + diff --git a/Code/Rodney/Django/Blog/users_app/migrations/0001_initial.py b/Code/Rodney/Django/Blog/users_app/migrations/0001_initial.py new file mode 100644 index 00000000..8e86fb68 --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# Generated by Django 4.0.1 on 2022-01-04 20:11 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('user_blog_name', models.CharField(max_length=200)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/Code/Rodney/Django/Blog/users_app/migrations/0002_blogpost.py b/Code/Rodney/Django/Blog/users_app/migrations/0002_blogpost.py new file mode 100644 index 00000000..ad6392c8 --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/migrations/0002_blogpost.py @@ -0,0 +1,27 @@ +# Generated by Django 4.0.1 on 2022-01-08 03:45 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('users_app', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='BlogPost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('blog_title', models.CharField(max_length=200)), + ('blog_body', models.TextField(max_length=5000)), + ('public_post', models.BooleanField()), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('edited_date', models.DateTimeField(auto_now=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blogtitle', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/Code/Rodney/Django/Blog/users_app/migrations/0003_delete_blogpost.py b/Code/Rodney/Django/Blog/users_app/migrations/0003_delete_blogpost.py new file mode 100644 index 00000000..80a148c1 --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/migrations/0003_delete_blogpost.py @@ -0,0 +1,16 @@ +# Generated by Django 4.0.1 on 2022-01-09 16:22 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users_app', '0002_blogpost'), + ] + + operations = [ + migrations.DeleteModel( + name='BlogPost', + ), + ] diff --git a/Code/Rodney/Django/Blog/users_app/migrations/__init__.py b/Code/Rodney/Django/Blog/users_app/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Blog/users_app/models.py b/Code/Rodney/Django/Blog/users_app/models.py new file mode 100644 index 00000000..00e2339f --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/models.py @@ -0,0 +1,14 @@ +from django.db import models + +from django.contrib.auth.models import AbstractUser +from django.contrib.auth import get_user_model +from django.db.models.fields import BooleanField, TextField + + +class User(AbstractUser): + + user_blog_name = models.CharField(max_length=200) + + def __str__(self): + return self.username + diff --git a/Code/Rodney/Django/Blog/users_app/tests.py b/Code/Rodney/Django/Blog/users_app/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Rodney/Django/Blog/users_app/urls.py b/Code/Rodney/Django/Blog/users_app/urls.py new file mode 100644 index 00000000..8de90b9f --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/urls.py @@ -0,0 +1,16 @@ + +from django.urls import path +from django.urls.resolvers import URLPattern + +from . import views + + +app_name = 'users_app' + +urlpatterns = [ + path('register/', views.register, name='register'), + path('login/', views.login, name='login'), + path('/', views.userprofile, name='userprofile'), + path('logout', views.logout, name='logout'), + +] \ No newline at end of file diff --git a/Code/Rodney/Django/Blog/users_app/views.py b/Code/Rodney/Django/Blog/users_app/views.py new file mode 100644 index 00000000..a17abc0c --- /dev/null +++ b/Code/Rodney/Django/Blog/users_app/views.py @@ -0,0 +1,115 @@ +from django.contrib import auth +from django.shortcuts import get_object_or_404, render, redirect +from django.urls import reverse +from django.http import HttpResponse +from .forms import UserAuthForm, UserForm +from django.contrib.auth import (authenticate, get_user_model, +login as django_login, +logout as django_logout ) +from django.contrib.auth.decorators import login_required + + + + + +def register(request): + ## this is creating an empty form when user lands on page + + form = UserAuthForm() + + if request.method == 'GET': + + context = { + 'form': form + } + + return render(request, 'users/register.html', context) + + + if request.method == 'POST': + + ##creating a UserAuthForm with HTML form data + + form = UserAuthForm(request.POST) + + if form.is_valid(): + ## commit false creates object, doesn't save it + new_user = form.save(commit=False) + + ## after you get form is valid, you get access to cleaned_data dictionary + new_user.set_password(form.cleaned_data['password']) + + #without this, you would just have a plain text password which won't allow you to log in ?? (I think) + + + ## now save the new user object to the database + new_user.save() + + ## redirect does same as HTTP Response Redirect, a short cut + + return redirect(reverse('users_app:login')) + + else: + + context = { + + 'form': UserAuthForm(), + 'errors': ['User already exists! Please try again'] + } + + return render (request, 'users/register.html', context) + +def login(request): + + if request.method == 'GET': + + form = UserAuthForm() + + context = { + 'form': form + } + + return render(request, 'users/login.html', context) + + if request.method == 'POST': + + ## get form data from request + + form = request.POST + + username = form['username'] + password = form['password'] + + ## try to authenticate user + user = authenticate(request, username=username, password=password) + + ## if credentials not valid, return error + if user is None: + context = { + 'form': UserAuthForm(), + 'errors': ['Invalid Username or Password'] + } + + return render(request, 'users/login.html', context) + + else: + + django_login(request, user) + + # redirect to users profile page + return redirect(reverse('users_app:userprofile', kwargs={'username': user.username})) + +@login_required +def userprofile(request, username): + # find the user that just logged in + user = get_object_or_404(get_user_model(), username=username) + + # print(user.blogpost.all()) + + return render(request, 'users/userprofile.html', {'user': user}) + +def logout(request): + django_logout(request) + + return redirect(reverse('users_app:login')) + diff --git a/Code/Rodney/Django/Pokedex/fetch_pokemon.py b/Code/Rodney/Django/Pokedex/fetch_pokemon.py new file mode 100644 index 00000000..066e9f81 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/fetch_pokemon.py @@ -0,0 +1,46 @@ + + + +import requests +import json +import pyperclip + +data = {'pokemon':[]} +num_pokemon = 152 +for i in range(1, num_pokemon): + # get the data from the pokemon api + response = requests.get('https://pokeapi.co/api/v2/pokemon/' + str(i)) + pokeapi_data = json.loads(response.text) + + # extract the relevant portions of data + number = pokeapi_data['id'] + name = pokeapi_data['name'] + height = pokeapi_data['height'] + weight = pokeapi_data['weight'] + image_front = pokeapi_data['sprites']['front_default'] + image_back = pokeapi_data['sprites']['back_default'] + url = 'https://pokemon.fandom.com/wiki/' + name + types = [type['type']['name'] for type in pokeapi_data['types']] + + # put the relevant data into a dictionary + pokemon = { + 'number': number, + 'name': name, + 'height': height, + 'weight': weight, + 'image_front': image_front, + 'image_back': image_back, + 'types': types, + 'url': url + } + + # add the pokemon to our list + data['pokemon'].append(pokemon) + + # give the user some feedback + print(str(round(i/num_pokemon*100,2))+'%') + +# copy the resulting json to the clipboard +pyperclip.copy(json.dumps(data, indent=4)) + + diff --git a/Code/Rodney/Django/Pokedex/manage.py b/Code/Rodney/Django/Pokedex/manage.py new file mode 100755 index 00000000..6adc02e8 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pokedex.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) + + +if __name__ == '__main__': + main() diff --git a/Code/Rodney/Django/Pokedex/pokedex/__init__.py b/Code/Rodney/Django/Pokedex/pokedex/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Pokedex/pokedex/asgi.py b/Code/Rodney/Django/Pokedex/pokedex/asgi.py new file mode 100644 index 00000000..03bf1b13 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for pokedex project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pokedex.settings') + +application = get_asgi_application() diff --git a/Code/Rodney/Django/Pokedex/pokedex/settings.py b/Code/Rodney/Django/Pokedex/pokedex/settings.py new file mode 100644 index 00000000..d7a979c9 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex/settings.py @@ -0,0 +1,126 @@ +""" +Django settings for pokedex project. + +Generated by 'django-admin startproject' using Django 4.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-4n%)5(_x(g77&xo#af(t9pkj+^h*1a*(_^nln2*8(yib4nybl8' + +# 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', + 'pokedex_app', +] + +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 = 'pokedex.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [str(BASE_DIR.joinpath('templates'))], + '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', + ], + }, + }, +] + +WSGI_APPLICATION = 'pokedex.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/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/4.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'America/Detroit' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = 'static/' + +STATICFILES_DIRS = [str(BASE_DIR.joinpath('static'))] + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/Code/Rodney/Django/Pokedex/pokedex/urls.py b/Code/Rodney/Django/Pokedex/pokedex/urls.py new file mode 100644 index 00000000..fd715fe9 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex/urls.py @@ -0,0 +1,23 @@ +"""pokedex URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.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 + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('pokedex_app.urls')), + +] diff --git a/Code/Rodney/Django/Pokedex/pokedex/wsgi.py b/Code/Rodney/Django/Pokedex/pokedex/wsgi.py new file mode 100644 index 00000000..8b6d1551 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for pokedex 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/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pokedex.settings') + +application = get_wsgi_application() diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/__init__.py b/Code/Rodney/Django/Pokedex/pokedex_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/admin.py b/Code/Rodney/Django/Pokedex/pokedex_app/admin.py new file mode 100644 index 00000000..81e211fc --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin + +from .models import PokemonType, Pokemon + +admin.site.register(PokemonType) +admin.site.register(Pokemon) \ No newline at end of file diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/apps.py b/Code/Rodney/Django/Pokedex/pokedex_app/apps.py new file mode 100644 index 00000000..14f7ad62 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PokedexAppConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'pokedex_app' diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/management/commands/populate_db.py b/Code/Rodney/Django/Pokedex/pokedex_app/management/commands/populate_db.py new file mode 100644 index 00000000..e29c69f5 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/management/commands/populate_db.py @@ -0,0 +1,44 @@ +from django.core.management.base import BaseCommand +import requests +from pokedex_app.models import Pokemon, PokemonType +import json + +class Command(BaseCommand): + + def handle(self, *args, **options): + + Pokemon.objects.all().delete() + PokemonType.objects.all().delete() + + with open('static/pokemon.json') as pokemon_file: + pokemons = json.loads(pokemon_file.read()) + for pokemon in pokemons['pokemon']: + number = int(pokemon['number']) + name = pokemon['name'] + height = float(pokemon['height']) + weight = float(pokemon['weight']) + image_front = pokemon['image_front'] + image_back = pokemon['image_back'] + types = pokemon['types'] + url = pokemon['url'] + + pokemon = Pokemon.objects.create( + number = number, + name = name, + height = height, + weight = weight, + image_front = image_front, + image_back = image_back, + url = url + ) + + for type in types: + type, created = PokemonType.objects.get_or_create(name=type) + + if type not in pokemon.types.all(): + pokemon.types.add(type) + + + + + \ No newline at end of file diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0001_initial.py b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0001_initial.py new file mode 100644 index 00000000..82cf23e3 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 4.0.1 on 2022-01-11 22:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='PokemonType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Pokemon', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.IntegerField()), + ('name', models.CharField(max_length=100)), + ('height', models.FloatField(default=0.0)), + ('weight', models.FloatField(default=0.0)), + ('image_front', models.CharField(max_length=200)), + ('image_back', models.CharField(max_length=200)), + ('types', models.ManyToManyField(related_name='pokemontype', to='pokedex_app.PokemonType')), + ], + ), + ] diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0002_alter_pokemon_types.py b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0002_alter_pokemon_types.py new file mode 100644 index 00000000..82e4857d --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0002_alter_pokemon_types.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.1 on 2022-01-13 03:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pokedex_app', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='pokemon', + name='types', + field=models.ManyToManyField(related_name='pokemon', to='pokedex_app.PokemonType'), + ), + ] diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0003_pokemon_url.py b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0003_pokemon_url.py new file mode 100644 index 00000000..6aeac466 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/0003_pokemon_url.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.1 on 2022-01-15 02:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pokedex_app', '0002_alter_pokemon_types'), + ] + + operations = [ + migrations.AddField( + model_name='pokemon', + name='url', + field=models.CharField(default='www.google.com', max_length=300), + preserve_default=False, + ), + ] diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/migrations/__init__.py b/Code/Rodney/Django/Pokedex/pokedex_app/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/models.py b/Code/Rodney/Django/Pokedex/pokedex_app/models.py new file mode 100644 index 00000000..e9556df7 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/models.py @@ -0,0 +1,26 @@ +from django.db import models +from django.db import models +from django.contrib.auth import get_user_model +from django.db.models.fields import TextField, IntegerField, FloatField +from django.db.models.fields.related import ManyToManyField + + + +class PokemonType(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + +class Pokemon(models.Model): + number = models.IntegerField() + name = models.CharField(max_length=100) + height = models.FloatField(default=0.0) + weight = models.FloatField(default=0.0) + image_front = models.CharField(max_length=200) + image_back = models.CharField(max_length=200) + url = models.CharField(max_length=300) + types = ManyToManyField(PokemonType, related_name='pokemon') + + def __str__(self): + return self.name \ No newline at end of file diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/pokemonurls.py b/Code/Rodney/Django/Pokedex/pokedex_app/pokemonurls.py new file mode 100644 index 00000000..df241618 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/pokemonurls.py @@ -0,0 +1,156 @@ + + + +urls = ['https://pokemon.fandom.com/wiki/bulbasaur', +'https://pokemon.fandom.com/wiki/ivysaur', +'https://pokemon.fandom.com/wiki/venusaur', +'https://pokemon.fandom.com/wiki/charmander', +'https://pokemon.fandom.com/wiki/charmeleon', +'https://pokemon.fandom.com/wiki/charizard', +'https://pokemon.fandom.com/wiki/squirtle', +'https://pokemon.fandom.com/wiki/wartortle', +'https://pokemon.fandom.com/wiki/blastoise', +'https://pokemon.fandom.com/wiki/caterpie', +'https://pokemon.fandom.com/wiki/metapod', +'https://pokemon.fandom.com/wiki/butterfree', +'https://pokemon.fandom.com/wiki/weedle', +'https://pokemon.fandom.com/wiki/kakuna', +'https://pokemon.fandom.com/wiki/beedrill', +'https://pokemon.fandom.com/wiki/pidgey', +'https://pokemon.fandom.com/wiki/pidgeotto', +'https://pokemon.fandom.com/wiki/pidgeot', +'https://pokemon.fandom.com/wiki/rattata', +'https://pokemon.fandom.com/wiki/raticate', +'https://pokemon.fandom.com/wiki/spearow', +'https://pokemon.fandom.com/wiki/fearow', +'https://pokemon.fandom.com/wiki/ekans', +'https://pokemon.fandom.com/wiki/arbok', +'https://pokemon.fandom.com/wiki/pikachu', +'https://pokemon.fandom.com/wiki/raichu', +'https://pokemon.fandom.com/wiki/sandshrew', +'https://pokemon.fandom.com/wiki/sandslash', +'https://pokemon.fandom.com/wiki/nidoran-f', +'https://pokemon.fandom.com/wiki/nidorina', +'https://pokemon.fandom.com/wiki/nidoqueen', +'https://pokemon.fandom.com/wiki/nidoran-m', +'https://pokemon.fandom.com/wiki/nidorino', +'https://pokemon.fandom.com/wiki/nidoking', +'https://pokemon.fandom.com/wiki/clefairy', +'https://pokemon.fandom.com/wiki/clefable', +'https://pokemon.fandom.com/wiki/vulpix', +'https://pokemon.fandom.com/wiki/ninetales', +'https://pokemon.fandom.com/wiki/jigglypuff', +'https://pokemon.fandom.com/wiki/wigglytuff', +'https://pokemon.fandom.com/wiki/zubat', +'https://pokemon.fandom.com/wiki/golbat', +'https://pokemon.fandom.com/wiki/oddish', +'https://pokemon.fandom.com/wiki/gloom', +'https://pokemon.fandom.com/wiki/vileplume', +'https://pokemon.fandom.com/wiki/paras', +'https://pokemon.fandom.com/wiki/parasect', +'https://pokemon.fandom.com/wiki/venonat', +'https://pokemon.fandom.com/wiki/venomoth', +'https://pokemon.fandom.com/wiki/diglett', +'https://pokemon.fandom.com/wiki/dugtrio', +'https://pokemon.fandom.com/wiki/meowth', +'https://pokemon.fandom.com/wiki/persian', +'https://pokemon.fandom.com/wiki/psyduck', +'https://pokemon.fandom.com/wiki/golduck', +'https://pokemon.fandom.com/wiki/mankey', +'https://pokemon.fandom.com/wiki/primeape', +'https://pokemon.fandom.com/wiki/growlithe', +'https://pokemon.fandom.com/wiki/arcanine', +'https://pokemon.fandom.com/wiki/poliwag', +'https://pokemon.fandom.com/wiki/poliwhirl', +'https://pokemon.fandom.com/wiki/poliwrath', +'https://pokemon.fandom.com/wiki/abra', +'https://pokemon.fandom.com/wiki/kadabra', +'https://pokemon.fandom.com/wiki/alakazam', +'https://pokemon.fandom.com/wiki/machop', +'https://pokemon.fandom.com/wiki/machoke', +'https://pokemon.fandom.com/wiki/machamp', +'https://pokemon.fandom.com/wiki/bellsprout', +'https://pokemon.fandom.com/wiki/weepinbell', +'https://pokemon.fandom.com/wiki/victreebel', +'https://pokemon.fandom.com/wiki/tentacool', +'https://pokemon.fandom.com/wiki/tentacruel', +'https://pokemon.fandom.com/wiki/geodude', +'https://pokemon.fandom.com/wiki/graveler', +'https://pokemon.fandom.com/wiki/golem', +'https://pokemon.fandom.com/wiki/ponyta', +'https://pokemon.fandom.com/wiki/rapidash', +'https://pokemon.fandom.com/wiki/slowpoke', +'https://pokemon.fandom.com/wiki/slowbro', +'https://pokemon.fandom.com/wiki/magnemite', +'https://pokemon.fandom.com/wiki/magneton', +'https://pokemon.fandom.com/wiki/farfetchd', +'https://pokemon.fandom.com/wiki/doduo', +'https://pokemon.fandom.com/wiki/dodrio', +'https://pokemon.fandom.com/wiki/seel', +'https://pokemon.fandom.com/wiki/dewgong', +'https://pokemon.fandom.com/wiki/grimer', +'https://pokemon.fandom.com/wiki/muk', +'https://pokemon.fandom.com/wiki/shellder', +'https://pokemon.fandom.com/wiki/cloyster', +'https://pokemon.fandom.com/wiki/gastly', +'https://pokemon.fandom.com/wiki/haunter', +'https://pokemon.fandom.com/wiki/gengar', +'https://pokemon.fandom.com/wiki/onix', +'https://pokemon.fandom.com/wiki/drowzee', +'https://pokemon.fandom.com/wiki/hypno', +'https://pokemon.fandom.com/wiki/krabby', +'https://pokemon.fandom.com/wiki/kingler', +'https://pokemon.fandom.com/wiki/voltorb', +'https://pokemon.fandom.com/wiki/electrode', +'https://pokemon.fandom.com/wiki/exeggcute', +'https://pokemon.fandom.com/wiki/exeggutor', +'https://pokemon.fandom.com/wiki/cubone', +'https://pokemon.fandom.com/wiki/marowak', +'https://pokemon.fandom.com/wiki/hitmonlee', +'https://pokemon.fandom.com/wiki/hitmonchan', +'https://pokemon.fandom.com/wiki/lickitung', +'https://pokemon.fandom.com/wiki/koffing', +'https://pokemon.fandom.com/wiki/weezing', +'https://pokemon.fandom.com/wiki/rhyhorn', +'https://pokemon.fandom.com/wiki/rhydon', +'https://pokemon.fandom.com/wiki/chansey', +'https://pokemon.fandom.com/wiki/tangela', +'https://pokemon.fandom.com/wiki/kangaskhan', +'https://pokemon.fandom.com/wiki/horsea', +'https://pokemon.fandom.com/wiki/seadra', +'https://pokemon.fandom.com/wiki/goldeen', +'https://pokemon.fandom.com/wiki/seaking', +'https://pokemon.fandom.com/wiki/staryu', +'https://pokemon.fandom.com/wiki/starmie', +'https://pokemon.fandom.com/wiki/mr-mime', +'https://pokemon.fandom.com/wiki/scyther', +'https://pokemon.fandom.com/wiki/jynx', +'https://pokemon.fandom.com/wiki/electabuzz', +'https://pokemon.fandom.com/wiki/magmar', +'https://pokemon.fandom.com/wiki/pinsir', +'https://pokemon.fandom.com/wiki/tauros', +'https://pokemon.fandom.com/wiki/magikarp', +'https://pokemon.fandom.com/wiki/gyarados', +'https://pokemon.fandom.com/wiki/lapras', +'https://pokemon.fandom.com/wiki/ditto', +'https://pokemon.fandom.com/wiki/eevee', +'https://pokemon.fandom.com/wiki/vaporeon', +'https://pokemon.fandom.com/wiki/jolteon', +'https://pokemon.fandom.com/wiki/flareon', +'https://pokemon.fandom.com/wiki/porygon', +'https://pokemon.fandom.com/wiki/omanyte', +'https://pokemon.fandom.com/wiki/omastar', +'https://pokemon.fandom.com/wiki/kabuto', +'https://pokemon.fandom.com/wiki/kabutops', +'https://pokemon.fandom.com/wiki/aerodactyl', +'https://pokemon.fandom.com/wiki/snorlax', +'https://pokemon.fandom.com/wiki/articuno', +'https://pokemon.fandom.com/wiki/zapdos', +'https://pokemon.fandom.com/wiki/moltres', +'https://pokemon.fandom.com/wiki/dratini', +'https://pokemon.fandom.com/wiki/dragonair', +'https://pokemon.fandom.com/wiki/dragonite', +'https://pokemon.fandom.com/wiki/mewtwo', +'https://pokemon.fandom.com/wiki/mew'] + +print(urls[0]) \ No newline at end of file diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/tests.py b/Code/Rodney/Django/Pokedex/pokedex_app/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/urls.py b/Code/Rodney/Django/Pokedex/pokedex_app/urls.py new file mode 100644 index 00000000..ecd507ab --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/urls.py @@ -0,0 +1,17 @@ +from django.urls import path +from django.urls.resolvers import URLPattern + +from . import views + + +app_name = 'pokedex_app' + +urlpatterns = [ + path('', views.home, name='home'), + path('heightup/', views.heightup, name='heightup'), + path('heightdown/', views.heightdown, name='heightdown'), + path('weightup/', views.weightup, name='weightup'), + path('weightdown/', views.weightdown, name='weightdown'), + +] + diff --git a/Code/Rodney/Django/Pokedex/pokedex_app/views.py b/Code/Rodney/Django/Pokedex/pokedex_app/views.py new file mode 100644 index 00000000..e92f6fb6 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/pokedex_app/views.py @@ -0,0 +1,189 @@ +from base64 import urlsafe_b64decode +import py_compile +from turtle import title +from django.contrib import auth +from django.shortcuts import get_object_or_404, render, redirect +from django.urls import reverse +from django.http import HttpResponse +from django.contrib.auth import authenticate +from .models import PokemonType, Pokemon +from django.db.models import Q +from django.core.paginator import Paginator + + +def home(request, page_num=1, per_page=10): + + form = { + 'search_name': request.POST.get('search_name') or '', + # 'types': request.POST.getlist('types') or [type.name for type in PokemonType.objects.all()], + } + + search_name = form.get('search_name') + # search_types = form.get('types') + + pokemons = Pokemon.objects.all() + + # if search_types: + # pokemons = Pokemon.objects.filter(types__name__in=search_types) + + if search_name: + pokemons = pokemons.filter(name__icontains=search_name) + + for pokemon in pokemons: + pokemon.weight = (pokemon.weight/10) * 2.2 + pokemon.weight = int(pokemon.weight) + + for pokemon in pokemons: + pokemon.height = (pokemon.height/10) * 39.37 + pokemon.height = int(pokemon.height) + if pokemon.height > 12: + pokemon_feet = pokemon.height//12 + pokemon_inches = pokemon.height%12 + pokemon.height = (f'{pokemon_feet} ft. {pokemon_inches} in.') + else: + pokemon.height = (f'{pokemon.height} in.') + + + for pokemon in pokemons: + pokemon.type = pokemon.types.all() + + page_num = request.GET.get('page_num') or page_num + per_page = request.GET.get('per_page') or per_page + + pokemons_page = Paginator(pokemons, per_page).get_page(page_num) + + context = { + 'pokemons_page': pokemons_page, + } + + return render(request, 'pokedex_home/home.html', context ) + + +def heightup(request, page_num=1, per_page=10): + + page_num = request.GET.get('page_num') or page_num + per_page = request.GET.get('per_page') or per_page + + pokemons = Pokemon.objects.all().order_by('-height') + + for pokemon in pokemons: + pokemon.weight = (pokemon.weight/10) * 2.2 + pokemon.weight = int(pokemon.weight) + + for pokemon in pokemons: + pokemon.height = (pokemon.height/10) * 39.37 + pokemon.height = int(pokemon.height) + if pokemon.height > 12: + pokemon_feet = pokemon.height//12 + pokemon_inches = pokemon.height%12 + pokemon.height = (f'{pokemon_feet} ft. {pokemon_inches} in.') + else: + pokemon.height = (f'{pokemon.height} in.') + + for pokemon in pokemons: + pokemon.type = pokemon.types.all() + + pokemons_page = Paginator(pokemons, per_page).get_page(page_num) + + print(per_page) + + context = { + 'pokemons_page': pokemons_page, + } + return render(request, 'pokedex_home/home.html', context ) + +def heightdown(request, page_num=1, per_page=10): + + page_num = request.GET.get('page_num') or page_num + per_page = request.GET.get('per_page') or per_page + + pokemons = Pokemon.objects.all().order_by('height') + + for pokemon in pokemons: + pokemon.weight = (pokemon.weight/10) * 2.2 + pokemon.weight = int(pokemon.weight) + + for pokemon in pokemons: + pokemon.height = (pokemon.height/10) * 39.37 + pokemon.height = int(pokemon.height) + if pokemon.height > 12: + pokemon_feet = pokemon.height//12 + pokemon_inches = pokemon.height%12 + pokemon.height = (f'{pokemon_feet} ft. {pokemon_inches} in.') + else: + pokemon.height = (f'{pokemon.height} in.') + + for pokemon in pokemons: + pokemon.type = pokemon.types.all() + + pokemons_page = Paginator(pokemons, per_page).get_page(page_num) + + context = { + 'pokemons_page': pokemons_page, + } + return render(request, 'pokedex_home/home.html', context ) + +def weightup(request, page_num=1, per_page=10): + + page_num = request.GET.get('page_num') or page_num + per_page = request.GET.get('per_page') or per_page + + pokemons = Pokemon.objects.all().order_by('-weight') + + for pokemon in pokemons: + pokemon.weight = (pokemon.weight/10) * 2.2 + pokemon.weight = int(pokemon.weight) + + for pokemon in pokemons: + pokemon.height = (pokemon.height/10) * 39.37 + pokemon.height = int(pokemon.height) + if pokemon.height > 12: + pokemon_feet = pokemon.height//12 + pokemon_inches = pokemon.height%12 + pokemon.height = (f'{pokemon_feet} ft. {pokemon_inches} in.') + else: + pokemon.height = (f'{pokemon.height} in.') + + for pokemon in pokemons: + pokemon.type = pokemon.types.all() + + pokemons_page = Paginator(pokemons, per_page).get_page(page_num) + + context = { + 'pokemons_page': pokemons_page, + } + return render(request, 'pokedex_home/home.html', context ) + +def weightdown(request, page_num=1, per_page=10): + + page_num = request.GET.get('page_num') or page_num + per_page = request.GET.get('per_page') or per_page + + pokemons = Pokemon.objects.all().order_by('weight') + + for pokemon in pokemons: + pokemon.weight = (pokemon.weight/10) * 2.2 + pokemon.weight = int(pokemon.weight) + + for pokemon in pokemons: + pokemon.height = (pokemon.height/10) * 39.37 + pokemon.height = int(pokemon.height) + if pokemon.height > 12: + pokemon_feet = pokemon.height//12 + pokemon_inches = pokemon.height%12 + pokemon.height = (f'{pokemon_feet} ft. {pokemon_inches} in.') + else: + pokemon.height = (f'{pokemon.height} in.') + + for pokemon in pokemons: + pokemon.type = pokemon.types.all() + + pokemons_page = Paginator(pokemons, per_page).get_page(page_num) + + context = { + 'pokemons_page': pokemons_page, + } + return render(request, 'pokedex_home/home.html', context ) + + + diff --git a/Code/Rodney/Django/Pokedex/static/css/index.css b/Code/Rodney/Django/Pokedex/static/css/index.css new file mode 100644 index 00000000..03ed5ddb --- /dev/null +++ b/Code/Rodney/Django/Pokedex/static/css/index.css @@ -0,0 +1,5 @@ + +* { + font-family: Verdana, Geneva, Tahoma, sans-serif; + font-size: 1.1rem; +} diff --git a/Code/Rodney/Django/Pokedex/static/pokemon.json b/Code/Rodney/Django/Pokedex/static/pokemon.json new file mode 100644 index 00000000..0e383d62 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/static/pokemon.json @@ -0,0 +1,1883 @@ +{ + "pokemon": [ + { + "number": 1, + "name": "bulbasaur", + "height": 7, + "weight": 69, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/bulbasaur" + }, + { + "number": 2, + "name": "ivysaur", + "height": 10, + "weight": 130, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/2.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/2.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/ivysaur" + }, + { + "number": 3, + "name": "venusaur", + "height": 20, + "weight": 1000, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/3.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/3.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/venusaur" + }, + { + "number": 4, + "name": "charmander", + "height": 6, + "weight": 85, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/4.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/charmander" + }, + { + "number": 5, + "name": "charmeleon", + "height": 11, + "weight": 190, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/5.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/5.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/charmeleon" + }, + { + "number": 6, + "name": "charizard", + "height": 17, + "weight": 905, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/6.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/6.png", + "types": [ + "flying", + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/charizard" + }, + { + "number": 7, + "name": "squirtle", + "height": 5, + "weight": 90, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/7.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/7.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/squirtle" + }, + { + "number": 8, + "name": "wartortle", + "height": 10, + "weight": 225, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/8.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/8.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/wartortle" + }, + { + "number": 9, + "name": "blastoise", + "height": 16, + "weight": 855, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/9.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/9.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/blastoise" + }, + { + "number": 10, + "name": "caterpie", + "height": 3, + "weight": 29, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/10.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/10.png", + "types": [ + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/caterpie" + }, + { + "number": 11, + "name": "metapod", + "height": 7, + "weight": 99, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/11.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/11.png", + "types": [ + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/metapod" + }, + { + "number": 12, + "name": "butterfree", + "height": 11, + "weight": 320, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/12.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/12.png", + "types": [ + "flying", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/butterfree" + }, + { + "number": 13, + "name": "weedle", + "height": 3, + "weight": 32, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/13.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/13.png", + "types": [ + "poison", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/weedle" + }, + { + "number": 14, + "name": "kakuna", + "height": 6, + "weight": 100, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/14.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/14.png", + "types": [ + "poison", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/kakuna" + }, + { + "number": 15, + "name": "beedrill", + "height": 10, + "weight": 295, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/15.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/15.png", + "types": [ + "poison", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/beedrill" + }, + { + "number": 16, + "name": "pidgey", + "height": 3, + "weight": 18, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/16.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/16.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/pidgey" + }, + { + "number": 17, + "name": "pidgeotto", + "height": 11, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/17.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/17.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/pidgeotto" + }, + { + "number": 18, + "name": "pidgeot", + "height": 15, + "weight": 395, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/18.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/18.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/pidgeot" + }, + { + "number": 19, + "name": "rattata", + "height": 3, + "weight": 35, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/19.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/19.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/rattata" + }, + { + "number": 20, + "name": "raticate", + "height": 7, + "weight": 185, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/20.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/20.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/raticate" + }, + { + "number": 21, + "name": "spearow", + "height": 3, + "weight": 20, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/21.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/21.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/spearow" + }, + { + "number": 22, + "name": "fearow", + "height": 12, + "weight": 380, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/22.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/22.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/fearow" + }, + { + "number": 23, + "name": "ekans", + "height": 20, + "weight": 69, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/23.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/23.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/ekans" + }, + { + "number": 24, + "name": "arbok", + "height": 35, + "weight": 650, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/24.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/24.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/arbok" + }, + { + "number": 25, + "name": "pikachu", + "height": 4, + "weight": 60, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/25.png", + "types": [ + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/pikachu" + }, + { + "number": 26, + "name": "raichu", + "height": 8, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/26.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/26.png", + "types": [ + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/raichu" + }, + { + "number": 27, + "name": "sandshrew", + "height": 6, + "weight": 120, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/27.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/27.png", + "types": [ + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/sandshrew" + }, + { + "number": 28, + "name": "sandslash", + "height": 10, + "weight": 295, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/28.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/28.png", + "types": [ + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/sandslash" + }, + { + "number": 29, + "name": "nidoran-f", + "height": 4, + "weight": 70, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/29.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/29.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/nidoran-f" + }, + { + "number": 30, + "name": "nidorina", + "height": 8, + "weight": 200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/30.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/30.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/nidorina" + }, + { + "number": 31, + "name": "nidoqueen", + "height": 13, + "weight": 600, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/31.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/31.png", + "types": [ + "ground", + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/nidoqueen" + }, + { + "number": 32, + "name": "nidoran-m", + "height": 5, + "weight": 90, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/32.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/32.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/nidoran-m" + }, + { + "number": 33, + "name": "nidorino", + "height": 9, + "weight": 195, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/33.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/33.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/nidorino" + }, + { + "number": 34, + "name": "nidoking", + "height": 14, + "weight": 620, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/34.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/34.png", + "types": [ + "ground", + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/nidoking" + }, + { + "number": 35, + "name": "clefairy", + "height": 6, + "weight": 75, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/35.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/35.png", + "types": [ + "fairy" + ], + "url": "https://pokemon.fandom.com/wiki/clefairy" + }, + { + "number": 36, + "name": "clefable", + "height": 13, + "weight": 400, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/36.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/36.png", + "types": [ + "fairy" + ], + "url": "https://pokemon.fandom.com/wiki/clefable" + }, + { + "number": 37, + "name": "vulpix", + "height": 6, + "weight": 99, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/37.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/37.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/vulpix" + }, + { + "number": 38, + "name": "ninetales", + "height": 11, + "weight": 199, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/38.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/38.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/ninetales" + }, + { + "number": 39, + "name": "jigglypuff", + "height": 5, + "weight": 55, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/39.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/39.png", + "types": [ + "fairy", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/jigglypuff" + }, + { + "number": 40, + "name": "wigglytuff", + "height": 10, + "weight": 120, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/40.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/40.png", + "types": [ + "fairy", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/wigglytuff" + }, + { + "number": 41, + "name": "zubat", + "height": 8, + "weight": 75, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/41.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/41.png", + "types": [ + "flying", + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/zubat" + }, + { + "number": 42, + "name": "golbat", + "height": 16, + "weight": 550, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/42.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/42.png", + "types": [ + "flying", + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/golbat" + }, + { + "number": 43, + "name": "oddish", + "height": 5, + "weight": 54, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/43.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/43.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/oddish" + }, + { + "number": 44, + "name": "gloom", + "height": 8, + "weight": 86, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/44.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/44.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/gloom" + }, + { + "number": 45, + "name": "vileplume", + "height": 12, + "weight": 186, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/45.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/45.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/vileplume" + }, + { + "number": 46, + "name": "paras", + "height": 3, + "weight": 54, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/46.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/46.png", + "types": [ + "grass", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/paras" + }, + { + "number": 47, + "name": "parasect", + "height": 10, + "weight": 295, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/47.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/47.png", + "types": [ + "grass", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/parasect" + }, + { + "number": 48, + "name": "venonat", + "height": 10, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/48.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/48.png", + "types": [ + "poison", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/venonat" + }, + { + "number": 49, + "name": "venomoth", + "height": 15, + "weight": 125, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/49.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/49.png", + "types": [ + "poison", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/venomoth" + }, + { + "number": 50, + "name": "diglett", + "height": 2, + "weight": 8, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/50.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/50.png", + "types": [ + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/diglett" + }, + { + "number": 51, + "name": "dugtrio", + "height": 7, + "weight": 333, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/51.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/51.png", + "types": [ + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/dugtrio" + }, + { + "number": 52, + "name": "meowth", + "height": 4, + "weight": 42, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/52.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/52.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/meowth" + }, + { + "number": 53, + "name": "persian", + "height": 10, + "weight": 320, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/53.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/53.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/persian" + }, + { + "number": 54, + "name": "psyduck", + "height": 8, + "weight": 196, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/54.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/54.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/psyduck" + }, + { + "number": 55, + "name": "golduck", + "height": 17, + "weight": 766, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/55.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/55.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/golduck" + }, + { + "number": 56, + "name": "mankey", + "height": 5, + "weight": 280, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/56.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/56.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/mankey" + }, + { + "number": 57, + "name": "primeape", + "height": 10, + "weight": 320, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/57.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/57.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/primeape" + }, + { + "number": 58, + "name": "growlithe", + "height": 7, + "weight": 190, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/58.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/58.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/growlithe" + }, + { + "number": 59, + "name": "arcanine", + "height": 19, + "weight": 1550, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/59.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/59.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/arcanine" + }, + { + "number": 60, + "name": "poliwag", + "height": 6, + "weight": 124, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/60.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/60.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/poliwag" + }, + { + "number": 61, + "name": "poliwhirl", + "height": 10, + "weight": 200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/61.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/61.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/poliwhirl" + }, + { + "number": 62, + "name": "poliwrath", + "height": 13, + "weight": 540, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/62.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/62.png", + "types": [ + "fighting", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/poliwrath" + }, + { + "number": 63, + "name": "abra", + "height": 9, + "weight": 195, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/63.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/63.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/abra" + }, + { + "number": 64, + "name": "kadabra", + "height": 13, + "weight": 565, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/64.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/64.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/kadabra" + }, + { + "number": 65, + "name": "alakazam", + "height": 15, + "weight": 480, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/65.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/65.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/alakazam" + }, + { + "number": 66, + "name": "machop", + "height": 8, + "weight": 195, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/66.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/66.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/machop" + }, + { + "number": 67, + "name": "machoke", + "height": 15, + "weight": 705, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/67.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/67.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/machoke" + }, + { + "number": 68, + "name": "machamp", + "height": 16, + "weight": 1300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/68.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/68.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/machamp" + }, + { + "number": 69, + "name": "bellsprout", + "height": 7, + "weight": 40, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/69.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/69.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/bellsprout" + }, + { + "number": 70, + "name": "weepinbell", + "height": 10, + "weight": 64, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/70.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/70.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/weepinbell" + }, + { + "number": 71, + "name": "victreebel", + "height": 17, + "weight": 155, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/71.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/71.png", + "types": [ + "poison", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/victreebel" + }, + { + "number": 72, + "name": "tentacool", + "height": 9, + "weight": 455, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/72.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/72.png", + "types": [ + "poison", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/tentacool" + }, + { + "number": 73, + "name": "tentacruel", + "height": 16, + "weight": 550, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/73.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/73.png", + "types": [ + "poison", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/tentacruel" + }, + { + "number": 74, + "name": "geodude", + "height": 4, + "weight": 200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/74.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/74.png", + "types": [ + "ground", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/geodude" + }, + { + "number": 75, + "name": "graveler", + "height": 10, + "weight": 1050, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/75.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/75.png", + "types": [ + "ground", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/graveler" + }, + { + "number": 76, + "name": "golem", + "height": 14, + "weight": 3000, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/76.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/76.png", + "types": [ + "ground", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/golem" + }, + { + "number": 77, + "name": "ponyta", + "height": 10, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/77.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/77.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/ponyta" + }, + { + "number": 78, + "name": "rapidash", + "height": 17, + "weight": 950, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/78.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/78.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/rapidash" + }, + { + "number": 79, + "name": "slowpoke", + "height": 12, + "weight": 360, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/79.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/79.png", + "types": [ + "psychic", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/slowpoke" + }, + { + "number": 80, + "name": "slowbro", + "height": 16, + "weight": 785, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/80.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/80.png", + "types": [ + "psychic", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/slowbro" + }, + { + "number": 81, + "name": "magnemite", + "height": 3, + "weight": 60, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/81.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/81.png", + "types": [ + "steel", + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/magnemite" + }, + { + "number": 82, + "name": "magneton", + "height": 10, + "weight": 600, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/82.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/82.png", + "types": [ + "steel", + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/magneton" + }, + { + "number": 83, + "name": "farfetchd", + "height": 8, + "weight": 150, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/83.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/83.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/farfetchd" + }, + { + "number": 84, + "name": "doduo", + "height": 14, + "weight": 392, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/84.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/84.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/doduo" + }, + { + "number": 85, + "name": "dodrio", + "height": 18, + "weight": 852, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/85.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/85.png", + "types": [ + "flying", + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/dodrio" + }, + { + "number": 86, + "name": "seel", + "height": 11, + "weight": 900, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/86.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/86.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/seel" + }, + { + "number": 87, + "name": "dewgong", + "height": 17, + "weight": 1200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/87.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/87.png", + "types": [ + "ice", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/dewgong" + }, + { + "number": 88, + "name": "grimer", + "height": 9, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/88.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/88.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/grimer" + }, + { + "number": 89, + "name": "muk", + "height": 12, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/89.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/89.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/muk" + }, + { + "number": 90, + "name": "shellder", + "height": 3, + "weight": 40, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/90.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/90.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/shellder" + }, + { + "number": 91, + "name": "cloyster", + "height": 15, + "weight": 1325, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/91.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/91.png", + "types": [ + "ice", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/cloyster" + }, + { + "number": 92, + "name": "gastly", + "height": 13, + "weight": 1, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/92.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/92.png", + "types": [ + "poison", + "ghost" + ], + "url": "https://pokemon.fandom.com/wiki/gastly" + }, + { + "number": 93, + "name": "haunter", + "height": 16, + "weight": 1, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/93.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/93.png", + "types": [ + "poison", + "ghost" + ], + "url": "https://pokemon.fandom.com/wiki/haunter" + }, + { + "number": 94, + "name": "gengar", + "height": 15, + "weight": 405, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/94.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/94.png", + "types": [ + "poison", + "ghost" + ], + "url": "https://pokemon.fandom.com/wiki/gengar" + }, + { + "number": 95, + "name": "onix", + "height": 88, + "weight": 2100, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/95.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/95.png", + "types": [ + "ground", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/onix" + }, + { + "number": 96, + "name": "drowzee", + "height": 10, + "weight": 324, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/96.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/96.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/drowzee" + }, + { + "number": 97, + "name": "hypno", + "height": 16, + "weight": 756, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/97.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/97.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/hypno" + }, + { + "number": 98, + "name": "krabby", + "height": 4, + "weight": 65, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/98.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/98.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/krabby" + }, + { + "number": 99, + "name": "kingler", + "height": 13, + "weight": 600, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/99.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/99.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/kingler" + }, + { + "number": 100, + "name": "voltorb", + "height": 5, + "weight": 104, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/100.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/100.png", + "types": [ + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/voltorb" + }, + { + "number": 101, + "name": "electrode", + "height": 12, + "weight": 666, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/101.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/101.png", + "types": [ + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/electrode" + }, + { + "number": 102, + "name": "exeggcute", + "height": 4, + "weight": 25, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/102.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/102.png", + "types": [ + "psychic", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/exeggcute" + }, + { + "number": 103, + "name": "exeggutor", + "height": 20, + "weight": 1200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/103.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/103.png", + "types": [ + "psychic", + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/exeggutor" + }, + { + "number": 104, + "name": "cubone", + "height": 4, + "weight": 65, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/104.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/104.png", + "types": [ + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/cubone" + }, + { + "number": 105, + "name": "marowak", + "height": 10, + "weight": 450, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/105.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/105.png", + "types": [ + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/marowak" + }, + { + "number": 106, + "name": "hitmonlee", + "height": 15, + "weight": 498, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/106.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/106.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/hitmonlee" + }, + { + "number": 107, + "name": "hitmonchan", + "height": 14, + "weight": 502, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/107.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/107.png", + "types": [ + "fighting" + ], + "url": "https://pokemon.fandom.com/wiki/hitmonchan" + }, + { + "number": 108, + "name": "lickitung", + "height": 12, + "weight": 655, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/108.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/108.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/lickitung" + }, + { + "number": 109, + "name": "koffing", + "height": 6, + "weight": 10, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/109.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/109.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/koffing" + }, + { + "number": 110, + "name": "weezing", + "height": 12, + "weight": 95, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/110.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/110.png", + "types": [ + "poison" + ], + "url": "https://pokemon.fandom.com/wiki/weezing" + }, + { + "number": 111, + "name": "rhyhorn", + "height": 10, + "weight": 1150, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/111.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/111.png", + "types": [ + "rock", + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/rhyhorn" + }, + { + "number": 112, + "name": "rhydon", + "height": 19, + "weight": 1200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/112.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/112.png", + "types": [ + "rock", + "ground" + ], + "url": "https://pokemon.fandom.com/wiki/rhydon" + }, + { + "number": 113, + "name": "chansey", + "height": 11, + "weight": 346, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/113.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/113.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/chansey" + }, + { + "number": 114, + "name": "tangela", + "height": 10, + "weight": 350, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/114.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/114.png", + "types": [ + "grass" + ], + "url": "https://pokemon.fandom.com/wiki/tangela" + }, + { + "number": 115, + "name": "kangaskhan", + "height": 22, + "weight": 800, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/115.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/115.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/kangaskhan" + }, + { + "number": 116, + "name": "horsea", + "height": 4, + "weight": 80, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/116.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/116.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/horsea" + }, + { + "number": 117, + "name": "seadra", + "height": 12, + "weight": 250, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/117.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/117.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/seadra" + }, + { + "number": 118, + "name": "goldeen", + "height": 6, + "weight": 150, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/118.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/118.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/goldeen" + }, + { + "number": 119, + "name": "seaking", + "height": 13, + "weight": 390, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/119.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/119.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/seaking" + }, + { + "number": 120, + "name": "staryu", + "height": 8, + "weight": 345, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/120.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/120.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/staryu" + }, + { + "number": 121, + "name": "starmie", + "height": 11, + "weight": 800, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/121.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/121.png", + "types": [ + "psychic", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/starmie" + }, + { + "number": 122, + "name": "mr-mime", + "height": 13, + "weight": 545, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/122.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/122.png", + "types": [ + "fairy", + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/mr-mime" + }, + { + "number": 123, + "name": "scyther", + "height": 15, + "weight": 560, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/123.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/123.png", + "types": [ + "flying", + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/scyther" + }, + { + "number": 124, + "name": "jynx", + "height": 14, + "weight": 406, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/124.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/124.png", + "types": [ + "psychic", + "ice" + ], + "url": "https://pokemon.fandom.com/wiki/jynx" + }, + { + "number": 125, + "name": "electabuzz", + "height": 11, + "weight": 300, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/125.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/125.png", + "types": [ + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/electabuzz" + }, + { + "number": 126, + "name": "magmar", + "height": 13, + "weight": 445, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/126.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/126.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/magmar" + }, + { + "number": 127, + "name": "pinsir", + "height": 15, + "weight": 550, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/127.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/127.png", + "types": [ + "bug" + ], + "url": "https://pokemon.fandom.com/wiki/pinsir" + }, + { + "number": 128, + "name": "tauros", + "height": 14, + "weight": 884, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/128.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/128.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/tauros" + }, + { + "number": 129, + "name": "magikarp", + "height": 9, + "weight": 100, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/129.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/129.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/magikarp" + }, + { + "number": 130, + "name": "gyarados", + "height": 65, + "weight": 2350, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/130.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/130.png", + "types": [ + "flying", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/gyarados" + }, + { + "number": 131, + "name": "lapras", + "height": 25, + "weight": 2200, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/131.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/131.png", + "types": [ + "ice", + "water" + ], + "url": "https://pokemon.fandom.com/wiki/lapras" + }, + { + "number": 132, + "name": "ditto", + "height": 3, + "weight": 40, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/132.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/132.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/ditto" + }, + { + "number": 133, + "name": "eevee", + "height": 3, + "weight": 65, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/133.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/133.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/eevee" + }, + { + "number": 134, + "name": "vaporeon", + "height": 10, + "weight": 290, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/134.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/134.png", + "types": [ + "water" + ], + "url": "https://pokemon.fandom.com/wiki/vaporeon" + }, + { + "number": 135, + "name": "jolteon", + "height": 8, + "weight": 245, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/135.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/135.png", + "types": [ + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/jolteon" + }, + { + "number": 136, + "name": "flareon", + "height": 9, + "weight": 250, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/136.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/136.png", + "types": [ + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/flareon" + }, + { + "number": 137, + "name": "porygon", + "height": 8, + "weight": 365, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/137.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/137.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/porygon" + }, + { + "number": 138, + "name": "omanyte", + "height": 4, + "weight": 75, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/138.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/138.png", + "types": [ + "water", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/omanyte" + }, + { + "number": 139, + "name": "omastar", + "height": 10, + "weight": 350, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/139.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/139.png", + "types": [ + "water", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/omastar" + }, + { + "number": 140, + "name": "kabuto", + "height": 5, + "weight": 115, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/140.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/140.png", + "types": [ + "water", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/kabuto" + }, + { + "number": 141, + "name": "kabutops", + "height": 13, + "weight": 405, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/141.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/141.png", + "types": [ + "water", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/kabutops" + }, + { + "number": 142, + "name": "aerodactyl", + "height": 18, + "weight": 590, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/142.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/142.png", + "types": [ + "flying", + "rock" + ], + "url": "https://pokemon.fandom.com/wiki/aerodactyl" + }, + { + "number": 143, + "name": "snorlax", + "height": 21, + "weight": 4600, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/143.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/143.png", + "types": [ + "normal" + ], + "url": "https://pokemon.fandom.com/wiki/snorlax" + }, + { + "number": 144, + "name": "articuno", + "height": 17, + "weight": 554, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/144.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/144.png", + "types": [ + "flying", + "ice" + ], + "url": "https://pokemon.fandom.com/wiki/articuno" + }, + { + "number": 145, + "name": "zapdos", + "height": 16, + "weight": 526, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/145.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/145.png", + "types": [ + "flying", + "electric" + ], + "url": "https://pokemon.fandom.com/wiki/zapdos" + }, + { + "number": 146, + "name": "moltres", + "height": 20, + "weight": 600, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/146.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/146.png", + "types": [ + "flying", + "fire" + ], + "url": "https://pokemon.fandom.com/wiki/moltres" + }, + { + "number": 147, + "name": "dratini", + "height": 18, + "weight": 33, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/147.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/147.png", + "types": [ + "dragon" + ], + "url": "https://pokemon.fandom.com/wiki/dratini" + }, + { + "number": 148, + "name": "dragonair", + "height": 40, + "weight": 165, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/148.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/148.png", + "types": [ + "dragon" + ], + "url": "https://pokemon.fandom.com/wiki/dragonair" + }, + { + "number": 149, + "name": "dragonite", + "height": 22, + "weight": 2100, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/149.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/149.png", + "types": [ + "flying", + "dragon" + ], + "url": "https://pokemon.fandom.com/wiki/dragonite" + }, + { + "number": 150, + "name": "mewtwo", + "height": 20, + "weight": 1220, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/150.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/150.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/mewtwo" + }, + { + "number": 151, + "name": "mew", + "height": 4, + "weight": 40, + "image_front": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/151.png", + "image_back": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/151.png", + "types": [ + "psychic" + ], + "url": "https://pokemon.fandom.com/wiki/mew" + } + ] +} \ No newline at end of file diff --git a/Code/Rodney/Django/Pokedex/templates/base.html b/Code/Rodney/Django/Pokedex/templates/base.html new file mode 100644 index 00000000..77ed908d --- /dev/null +++ b/Code/Rodney/Django/Pokedex/templates/base.html @@ -0,0 +1,57 @@ +{% load static %} + + + + + + + + + + + + PokedexApp + + + + + + + + + +
+ + + + {% block content %} + + + + {% endblock content %} + + +
+ + + + + + + + + \ No newline at end of file diff --git a/Code/Rodney/Django/Pokedex/templates/pokedex_home/home.html b/Code/Rodney/Django/Pokedex/templates/pokedex_home/home.html new file mode 100644 index 00000000..14471723 --- /dev/null +++ b/Code/Rodney/Django/Pokedex/templates/pokedex_home/home.html @@ -0,0 +1,197 @@ +{% extends 'base.html' %} +{% load static %} + + +{% block content %} + + + + +
+ +
+ + + + + + + + + + + {% for pokemon in pokemons_page %} + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
Name: {{pokemon.name|title}}
+

+
+ Danger +
    +
  • Number: {{pokemon.number}}
  • +
  • Height: {{pokemon.height}}
  • +
  • Weight: {{pokemon.weight}} pounds
  • + + {% for type in pokemon.type %} +
    +
    + {{type}} +
    +
    + {% endfor %} +
+ +
+ + {% endfor %} + +
+ +
+ + +{% endblock content %} + + diff --git a/README.md b/README.md index 941ecbf9..e3df37df 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Weeks 6, 7, 8, 9: HTML/CSS/Flask Weeks 10, 11: Javascript -**Weeks 12, 13, 14, 15: Django** +Weeks 12, 13, 14, 15: Django -Weeks 16, 17, 18: Capstone project +**Weeks 16, 17, 18: Capstone project** ### Scheduled Holidays (no class) - **Nov 11** - Veterans Day @@ -81,9 +81,12 @@ Weeks 16, 17, 18: Capstone project
  • Lab 01 - To Do List
  • Lab 02 - Blog
  • +
  • Lab 03 - Pokedex
+### Capstone proposals due Friday, 2022-01-21 + ## Submitting your work Make sure all labs are located within `Class_Raven/Code/` @@ -195,6 +198,10 @@ Corrections will be made only to that particular branch. Django diff --git a/post-bootcamp-project-ideas.md b/post-bootcamp-project-ideas.md index e0eb2296..18fa5049 100644 --- a/post-bootcamp-project-ideas.md +++ b/post-bootcamp-project-ideas.md @@ -2,7 +2,8 @@ ## Content Management System (CMS) ## Library -## Twitter clone +## Twitter clone +## Craigslist clone ## REST API using Django REST Framework (Javascript front-end) ## HTML Form Generator ## CSV to JSON converter