diff --git a/3 JavaScript/docs/13 - Vue Basics.md b/3 JavaScript/docs/13 - Vue Basics.md new file mode 100644 index 00000000..8de0a009 --- /dev/null +++ b/3 JavaScript/docs/13 - Vue Basics.md @@ -0,0 +1,153 @@ +# Vue + +Vue is a JavaScript framework for building user interfaces. Vue is a MVVM framework (Model View View-model). This is a fancy +way of saying that you give Vue a JavaScript object that represents the state (or data, or model) of your application and a +template (or view) of the visual representation of your data. Vue provides a view-model that binds your data to your template +and syncs them to each other instantly as both update. This means that as the user updates the values of elements on the page +they are instantly reflected in the data object, and as you update the data object with event listeners or other methods the +webpage template is instantly re-rendered with the new data. This removes the need to do manual DOM manipluation, making it +easy to write more complicated user interfaces and interactive web pages. + +For more information about JavaScript frameworks and why to use them, go [here](https://www.academind.com/learn/javascript/jquery-future-angular-react-vue/). + +More detailed explanations and code examples can be found in the Vue guide [here](https://vuejs.org/v2/guide/). + +A YouTube series on Vue basics can be found [here](https://www.youtube.com/watch?v=5LYrN_cAJoA&list=PL4cUxeGkcC9gQcYgjhBoeQH7wiAyZNrYa) and the solution repo [here](https://github.com/iamshaunjp/vuejs-playlist/tree/lesson-1). + +## Installing + +Vue can be run from a CLI tool and it's own project like Django, but in class we will be running Vue from a CDN, loaded +in with a `script` tag. This means we can add Vue to an existing HTML page and add Vue functionality and features +at our own page, on top of our existing front-end knowledge. + +`` + +Also highly recommended: +* [Vuetur syntax highlighting and snippits for VS Code](https://marketplace.visualstudio.com/items?itemName=octref.vetur) +* [Vue devtools for Chrome/Firefox/etc](https://github.com/vuejs/vue-devtools) + +## Declarative Rendering + +``` +
+ {{ message }} +
+``` + +``` +var app = new Vue({ + el: '#app', + data: { + message: 'Hello Vue!' + } +}) +``` + +Looks a lot like Django, doesn't it? We instantiate a `new` Vue instance, and pass it a configuration object. `el` represents +the CSS selector of the element to create our Vue app inside. `data` is our model or state, kind of like the context in Django. +When the value of `message` changes, our page will automatically change to match. + +We can also use a *directive* called `v-bind` to bind attribute values to Vue's data model. + +``` +
+ + Hover your mouse over me for a few seconds + to see my dynamically bound title! + +
+``` + +``` +var app2 = new Vue({ + el: '#app-2', + data: { + message: 'You loaded this page on ' + new Date().toLocaleString() + } +}) +``` + +## Loops and Conditionals + +``` +
+ Now you see me +
+``` + +``` +var app3 = new Vue({ + el: '#app-3', + data: { + seen: true + } +}) +``` + +``` +
+
    +
  1. + {{ todo.text }} +
  2. +
+
+``` + +``` +var app4 = new Vue({ + el: '#app-4', + data: { + todos: [ + { text: 'Learn JavaScript' }, + { text: 'Learn Vue' }, + { text: 'Build something awesome' } + ] + } +}) +``` + +Again, this should feel familiar. Instead of using template tags, we're using *directives* that look like HTML attributes. +Note that unlike with templte tags, the directives go directly on the HTML elements. + +## Events and Input + +``` +
+

{{ message }}

+ +
+``` + +``` +var app5 = new Vue({ + el: '#app-5', + data: { + message: 'Hello Vue.js!' + }, + methods: { + reverseMessage: function () { + this.message = this.message.split('').reverse().join('') + } + } +}) +``` + +We can also give Vue as many *methods* as we want, which we can call in our template with the `v-on:` directive. +Finally, we can use the `v-model` directive to bind the value of an input element to our data model. + +``` +
+

{{ message }}

+ +
+``` + +``` +var app6 = new Vue({ + el: '#app-6', + data: { + message: 'Hello Vue!' + } +}) +``` diff --git a/3 JavaScript/docs/14 - Ajax.md b/3 JavaScript/docs/14 - Ajax.md new file mode 100644 index 00000000..d0593c58 --- /dev/null +++ b/3 JavaScript/docs/14 - Ajax.md @@ -0,0 +1,180 @@ + +# AJAX + +AJAX stands for "asynchronous javascript and XML", and allows you to execute HTTP requests from JavaScript. You can read more about AJAX [here](https://developer.mozilla.org/en-US/docs/AJAX/Getting_Started), [here](https://developer.mozilla.org/en-US/docs/AJAX) and [here](https://www.w3schools.com/xml/ajax_intro.asp). + + +- [AJAX in Axios](#ajax-in-axios) + - [Setting Query Parameters](#setting-query-parameters) + - [Setting Custom Request Headers](#setting-custom-request-headers) + - [Sending Data](#sending-data) +- [AJAX in Vanilla JavaScript](#ajax-in-vanilla-javascript) +- [AJAX in jQuery](#ajax-in-jquery) +- [CORS](#cors) +- [JSONP](#jsonp) + + + +## AJAX in Axios + +[Axios](https://github.com/axios/axios) is a JavaScript library which handles AJAX more succinctly. Ultimately it's built upon vanilla JavaScript, so it doesn't offer anything you can't otherwise do with vanilla JS. + +```es6 +axios({ + method: 'get', + url: 'https://favqs.com/api/qotd' +}).then((response) => { + console.log(response.data) +}) +``` + +### Setting Query Parameters + +You can set query parameters using string concatenation or by setting the `params` property. Just remember that if you use string concatenation, you may have to use `encodeURIComponent` if the value has special characters in it. If you use `params` with Axios, it will automatically encode your parameters for you. + +```javascript +let business_name = 'Schmitt & Associates' +let url1 = "http://example.com/?business=" + business_name +let url2 = "http://example.com/?business=" + encodeURIComponent(business_name) +console.log(url1) // INVALID: http://example.com/?business=Schmitt & Associates +console.log(url2) // VALID: http://example.com/?business=Schmitt%20%26%20Associates +``` + +The code below sends the request to `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy` + +```javascript +axios({ + method: 'get', + url: 'https://opentdb.com/api.php', + params: { + amount: 10, + category: 18, + difficulty: 'easy' + } +}).then((response) => { + this.questions = response.data.results +}) +``` + +### Setting Custom Request Headers + +Depending on the API specification, you may need to put an API key inside the headers of the request. + +```javascript +axios({ + method: 'get', + url: 'https://favqs.com/api/qotd', + headers: { + 'x-api-key': 'api_key' + } +}).then((response) => { + console.log(response.data) +}) +``` + +### Sending Data + +```javascript +axios({ + method: 'post', + url: 'https://favqs.com/api/qotd', + data: { + name: 'joe', + age: '34' + } +}).then((response) => { + console.log(response.data) +}) +``` + +## AJAX in Vanilla JavaScript + +Here's how to execute an AJAX request in native JavaScript. You can read more about XMLHttpRequest [here](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). If you have many query parameters, consider using a function to [convert an object](https://stackoverflow.com/questions/111529/how-to-create-query-parameters-in-javascript). Remember status 200 means 'success'. + +```javascript +let xhttp = new XMLHttpRequest(); +xhttp.onreadystatechange = function() { + if (this.readyState === 4 && this.status === 200) { + console.log(this.responseText); + } +}; +xhttp.open("GET", 'https://api.iify.org/?format=json'); +xhttp.send(); +``` + +The possible values for `readyState` are shown below, you can find more info [here](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState) +- 0 UNSENT: the request has not yet been sent +- 1 OPENED: the connection has been opened +- 2 HEADERS_RECEIVED: the response headers have been received +- 3 LOADING: the response body is loading +- 4 DONE: the request has been completed + + +Adding a key-value pair to the request header is done by invoking `setRequestHeader` when the connection is open. + +```javascript +let xhttp = new XMLHttpRequest(); +xhttp.onreadystatechange = function() { + if (this.readyState === 1) { + xhttp.setRequestHeader('Authorization', 'Token token="a1b2c3"') + } else if (this.readyState === 4 && this.status === 200) { + let data = JSON.parse(xhttp.responseText); + // do something with data + } else if (this.readyState === 4 && this.status === 404) { + // handle 404 + } +}; +xhttp.open("GET", url); +xhttp.send(); +``` + + +It's possible to hide the messiness inside a function by passing a callback function. + +```javascript +function http_get(url, success) { + let xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState === 4 && this.status === 200) { + let data = JSON.parse(xhttp.responseText); + success(data); + } + }; + xhttp.open("GET", url); + xhttp.send(); +} + +http_get("https://api.ipify.org/?format=json", function(data) { + console.log(data); +}); +``` + + +## AJAX in jQuery + +```javascript +$.ajax({ + method: "GET", // specify the HTTP Verb + url: 'https://api.iify.org/?format=json' // specify the URL +}).done(function(data) { + console.log(data); // log the data we get in response +}).fail(function() { + alert("error"); // indicate that an error has occurred +}); +``` + + + + + +## CORS + +CORS stands for 'cross-origin resources sharing', and represents a relaxation of the [same origin policy](https://en.wikipedia.org/wiki/Same-origin_policy). You can read more about CORS on the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). + +If you receive the response "No 'Access-Control-Allow-Origin' header is present on the requested resource", it's because the remote server you're sending to the request from has security controls in place that prevent access from a client. You can read more about CORS [here](https://stackoverflow.com/questions/43871637/no-access-control-allow-origin-header-is-present-on-the-requested-resource-whe) and [here](https://security.stackexchange.com/questions/108835/how-does-cors-prevent-xss). + + +## JSONP + +JSONP (short for "JSON with Padding") is an additional security feature some APIs offer or require. You can read more about JSONP [here](https://stackoverflow.com/questions/3839966/can-anyone-explain-what-jsonp-is-in-layman-terms) and [here](https://stackoverflow.com/questions/16097763/jsonp-callback-function). + diff --git a/3 JavaScript/docs/More Ajax.md b/3 JavaScript/docs/More Ajax.md new file mode 100644 index 00000000..4d044777 --- /dev/null +++ b/3 JavaScript/docs/More Ajax.md @@ -0,0 +1,523 @@ + +# More ways to make an Ajax request + +## API + +API stands for "application programming interface", it's a standardized way to send and receive data from a web service via HTTP requests (GET, POST, PUT, DELETE). For example, try executing this url in your browser `http://api.forismatic.com/api/1.0/?method=getQuote&key=457653&format=json&lang=en`. This is an api for random inspiration quotes. Note the query parameters which specify a key, format, and language. When you enter it in your browser, you execute an HTTP GET request. You can do the same thing from JavaScript, then process the result and control how it's displayed to the user. Websites are for people, APIs are for programs. + +There are many free and open APIs available on the internet that can provide many different forms of data. You can find some public APIs [here](https://github.com/toddmotto/public-apis) and [here](https://catalog.data.gov/dataset?q=-aapi+api+OR++res_format%3Aapi#topic=developers_navigation). When trying to access a url, remember the [parts of a url](https://doepud.co.uk/images/blogs/complex_url.png). APIs can receive parameters through query parameters and headers. You can see query parameters in the example url. Adding parameters to the request header is done through the `setRequestHeader` function on the `XMLHttpRequest` object. + + + +### HTTP Methods + +HTTP requests include a **method**, which indicates what the intention of the request is. These methods are conventional. You could use `GET` to delete an entry in the database, but you shouldn't. You can find more info [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods), [here](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods). The most common HTTP methods parallel the CRUD operations we discussed in Python. + +| Method | Equivalent | +| --- | --- | +| POST | Create | +| GET | Retrieve | +| PUT | Update | +| DELETE | DELETE | + + +### HTTP Status Codes + +The response to an HTTP request will have a **status code** which indicates whether the request was successful or not, and what the error was. You can find a more thorough list of status codes [here](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes). + +| Code | Description | +| --- | --- | +| 1XX | information | +| 2XX | success | +| 3XX | redirection | +| 4XX | client error | +| 5XX | server error | + + +## AJAX + +AJAX stands for "asynchronous javascript and XML", and allows you to execute HTTP requests from JavaScript, without reloading the page. You can read more about AJAX [here](https://developer.mozilla.org/en-US/docs/AJAX/Getting_Started), [here](https://developer.mozilla.org/en-US/docs/AJAX) and [here](https://www.w3schools.com/xml/ajax_intro.asp). + +Here's how to execute an AJAX request in native JavaScript. You can read more about XMLHttpRequest [here](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). If you have many query parameters, consider using a function to [convert an object](https://stackoverflow.com/questions/111529/how-to-create-query-parameters-in-javascript). + +## Make a request + +### 1. Create a new XMLHttpRequest + +```javascript +let req = new XMLHttpRequest(); +``` + +### 2. Define event listeners + +Next, define your event listeners. There are four possible events to listen for: `progress`, `load`, `error`, and `abort`. + +- You will want to define a `load` listener, which will call some code when your response successfully loads. We will go over how to use the response data later in a callback later. +- `progress` events happen while you request is pending and waiting to receieve the entire response. This is useful for giving the user a "loading" or "please wait" message. +- `error` events happen if your request was not successful. +- `abort` events happen if your request is aborted before it has a chance to finish or error. + +```javascript +let req = new XMLHttpRequest(); + +req.addEventListener("progress", function(e) { + console.log(e.loaded); +}); +req.addEventListener("error", function(e) { + console.log(e.status); +}); +req.addEventListener("load", function(e) { + console.log(req.responseText); +}); +``` + +### 3. Open the request + +Next, we need to define the method (GET/POST/PUT/DELETE) of our request, and the URL to send our request to. + +Keep in mind that when working with an API, the exact URL used is how we define the data we're looking for. While you can pass a simple string of a URL in here, you may want to use either a template string to create a custom URL based on the values a user selects in some input field, or a function to transform an object into a URL query string. Be sure to review [parts of a url](https://doepud.co.uk/images/blogs/complex_url.png) and [converting an object into query paramters](https://stackoverflow.com/questions/111529/how-to-create-query-parameters-in-javascript). + +```javascript +let req = new XMLHttpRequest(); +req.addEventListener("progress", function(e) { + console.log(e.loaded); +}); +req.addEventListener("error", function(e) { + console.log(e.status); +}); +req.addEventListener("load", function(e) { + console.log(req.responseText); +}); + +req.open("GET", "https://favqs.com/api/qotd"); +``` + +### 4. Set any request headers + +Some APIs expect you to include some information encoded not as a URL, but as request headers. This can be more secure, as it hides things like authentication keys from the user. Most of the time this step is not required, but check the documentation of the API you want to use. For more details, check the [MDN](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader). + +```javascript +let req = new XMLHttpRequest(); +req.addEventListener("progress", function(e) { + console.log(e.loaded); +}); +req.addEventListener("error", function(e) { + console.log(e.status); +}); +req.addEventListener("load", function(e) { + console.log(req.responseText); +}); +req.open("GET", "https://favqs.com/api/qotd"); + +req.setRequestHeader("Authorization", 'Token token="YOUR_TOKEN_GOES_HERE"'); +``` + +### 5. Send the request + +This is the final step to make a request. Once the request is sent, the `progress`, `load`, `error`, and `abort` events will happen as the response is receieved and run their callback functions. + +Order matters! Make sure your request is in the following order, or you won't get the results you expect: +1. Create a new response +2. Add event listeners +3. Open the request +4. Set headers +5. Send the request + +```javascript +let req = new XMLHttpRequest(); +req.addEventListener("progress", function(e) { + console.log(e.loaded); +}); +req.addEventListener("error", function(e) { + console.log(e.status); +}); +req.addEventListener("load", function(e) { + console.log(req.responseText); +}); +req.open("GET", "https://favqs.com/api/qotd"); +req.setRequestHeader("Authorization", 'Token token="YOUR_TOKEN_GOES_HERE"'); +req.send() +``` + +## Handle the response + +Now that we've created a request successfully, we need to expand our callback functions to work with our new data, instead of just `console.log`-ing the response. + +### 1. JSON and XML + +JSON and XML are two popular ways of formatting data. Most APIs either return information in JSON or XML. + +[JSON](http://www.json.org/) is very similar to javascript object declarations, but the major difference is that the names are strings, and the values can only be numbers, strings, arrays, booleans, null, or objects. You can read more about the differences between JSON and JavaScript objects [here](https://stackoverflow.com/questions/8294088/javascript-object-vs-json). + +```json +{"employees":[ + { "firstName":"John", "lastName":"Doe" }, + { "firstName":"Anna", "lastName":"Smith" }, + { "firstName":"Peter", "lastName":"Jones" } +]} +``` + +[XML](https://developer.mozilla.org/en-US/docs/XML_Introduction) resembles HTML, it has tags, and attributes, and inner text. + +```xml + + + John + Doe + + + Anna + Smith + + + Peter + Jones + + +``` + +### 2. Parse the response + +Most web APIs will return JSON, and this one is no exception. However, the body of the response is stored as a text string. The first thing we need to do is parse the response text into a JavaScript object that we can use. + +```javascript +... + +req.addEventListener("load", function(e) { + console.log(req.responseText); // returns the raw text response + let response = JSON.parse(req.responseText); + console.log(response); // returns a JavaScript object +}); + +... +``` + +### 3. Use the parsed response + +Now we have usable data that we can access programatically within our JavaScript code. The next thing to do is to update the DOM with the new information. + +Here, I am selecting the element (in my case a `div`) to update, creating the HTML template string using the results for the response data, and finally updating the `innerHTML` of my target element with the HTML template string. + +```javascript +let target = document.getElementById("target"); // Define the HTML element to update + +... + +req.addEventListener("load", function(e) { + console.log(req.responseText); + let response = JSON.parse(req.responseText); + console.log(response); + + let resultHTML = ` +

${response.quote.body}

+

${response.quote.author}

+ ` + textTarget.innerHTML = resultHTML; +}); + +... +``` + +### 4. Update the other listeners too + +```javascript +... + +req.addEventListener("progress", function(e) { + console.log(e.loaded); + target.innerText = "Loading..."; +}); +req.addEventListener("error", function(e) { + console.log(e.status); + target.innerText = "Cannot load quote. Try again later!"; +}); +req.addEventListener("load", function(e) { + ... +}); + +... +``` + +### 5. Use an event to fire the request + +Here's our code so far: + +```javascript +let target = document.getElementById("target"); + +let req = new XMLHttpRequest(); +req.addEventListener("progress", function(e) { + console.log(e.loaded); + target.innerText = "Loading..."; +}); +req.addEventListener("error", function(e) { + console.log(e.status); + target.innerText = "Cannot load quote. Try again later!"; +}); +req.addEventListener("load", function(e) { + console.log(req.responseText); + let response = JSON.parse(req.responseText); + console.log(response); + + let resultHTML = ` +

${response.quote.body}

+

${response.quote.author}

+ ` + textTarget.innerHTML = resultHTML; +}); +req.open("GET", "https://favqs.com/api/qotd"); +req.setRequestHeader("Authorization", 'Token token="YOUR_TOKEN_GOES_HERE"'); +req.send() +``` + +Right now, our Ajax request is sent immediately as the page loads. This usually isn't the desired behavior. Most of the time an Ajax request is sent as the result of some sort of user input. I have a "get quote" button on my page, and I'm going to make this Ajax request send when the "get quote" button is pressed by the user. First, select the element and add an event listener. + +```javascript +let target = document.getElementById("target"); +let getQuoteButton = document.getElementById("quote-button"); + +getQuoteButton.addEventListener("click", function(e) { + // code goes here +}); +``` + +Next, put the entire body of code for making, parsing, and using a request inside the event listener. Now the page will only make the request and update the DOM using the response when the "get quote" button is clicked. + +```javascript +let target = document.getElementById("target"); +let getQuoteButton = document.getElementById("quote-button"); + +getQuoteButton.addEventListener("click", function(e) { + let req = new XMLHttpRequest(); + req.addEventListener("progress", function(e) { + console.log(e.loaded); + target.innerText = "Loading..."; + }); + req.addEventListener("error", function(e) { + console.log(e.status); + target.innerText = "Cannot load quote. Try again later!"; + }); + req.addEventListener("load", function(e) { + console.log(req.responseText); + let response = JSON.parse(req.responseText); + console.log(response); + let resultHTML = ` +

${response.quote.body}

+

${response.quote.author}

+ ` + textTarget.innerHTML = resultHTML; + }); + req.open("GET", "https://favqs.com/api/qotd"); + req.setRequestHeader("Authorization", 'Token token="YOUR_TOKEN_GOES_HERE"'); + req.send() +}); +``` + +## Advanced + +### Fetch and ES6 Promises +[Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) provides a newer, easier-to-use method to send HTTP requests and process their responses compared to `XMLHttpRequest`. + +Here's how to send a `GET` request and process it into JSON: +```js +fetch('https://api.ipify.org/?format=json') + .then(function(response) { + return response.json(); + }) + .then(function(myJson) { + console.log(myJson); + }) + .catch(error => console.error(error)); +``` +This is a basic `GET` request sent to `https://api.ipify.org/?format=json`. `fetch()` uses JS **Promises** to handle processing the request asynchronously. Any `.then(callback)` calls make sure to only process the callback after the previous function has been completed. `.catch(callback)` handles any errors returned from the request. The simplest use of `fetch()` takes one argument — the path to the resource you want to fetch — and returns a Promise containing the response (a Response object). + +#### Request options +The fetch() method can optionally accept a second parameter, an object that allows you to control a number of different settings. Some common parameters are below with their optional values. You can read more about others [**here**](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch). + +- method: [\*GET, POST, PUT, DELETE, etc.] +- mode: [no-cors, cors, \*same-origin] +- cache: [\*default, no-cache, reload, force-cache, only-if-cached] +- credentials: "same-origin", [include, same-origin, \*omit] +- redirect: [manual, \*follow, error] +- referrer: "no-referrer", // no-referrer, *client +- body: The body you want to add to your request (not available for GET or HEAD requests) +\*Note: Starred options are the default setting. + +This example is sending a `POST` request with some form data. +```js +let header = new Headers() +header.append('Authorization', 'Token token=""') +let form = new FormData(document.getElementById('todo-data')) + +fetch('https://example.com/new/', { + method: 'POST', + headers: header, + body: form +}).then((response) => console.log(response)) +.catch((err) => console.log(err)) +.then((response) => { + console.log('Success!') +}) +``` + +### async/await + +ECMAScript2017 (ES8) introduced an easier way to work with promises. + +#### Use `fetch` to make an AJAX request +Recall that `fetch()` is an *asynchronous function* that always returns a `Promise`. + +Handle promise the ES6 way: +```js +function getRandomQuote() { + // fetch() returns promise + fetch('http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1') + // .then() resolves promise with response object and returns another promise + .then(res => res.json()) + // console logs response JSON + .then(jsonRes => console.log(jsonRes)) + // catches any errors + .catch(err => console.log(err)); +} + +getRandomQuote(); +``` + +Using async/await: + +1. Use the `async` keyword at a function declaration to specify that the function contains some asynchronous operation. + +2. Add `await` keyword before any statement that returns a promise. Store the resolved values as variables. + +3. Treat other operations as synchronous operations. + +```js +async function getRandomQuote() { // Step 1 + const res = await fetch('http://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1'); // Step 2 + const jsonRes = await res.json(); // Step 2 + + console.log(jsonRes); // Step 3 +} + +getRandomQuote(); +``` + +### Axios + +[Axios](https://github.com/axios/axios) is a JavaScript library which handles AJAX more succinctly. Ultimately it's built upon the Vanilla JavaScript form of AJAX, so it doesn't offer anything you can't otherwise do with Vanilla. + +To use, you'll need to either install the library or use a **cdn**. + +Using cdn: +```html + +``` + +#### Examples +```javascript +axios.get(url) +.then(function (response) { + console.log(response.data) +}) +``` + +```javascript +let config = { + headers: { + 'x-api-key': api_key + } +} +axios.get(url, config) +.then(function (response) { + console.log(response.data) +}) +``` + +```javascript +let data = { + 'key': 'value' + 'data': 'to send' +} +let config = { + headers: { + 'header name': 'header value' + } +} +axios.post(url, data, config) +.then(function(response) { + console.log(response.data) +}) +``` + +For more information on Axios and the full list of options, be sure to check out the [official documentation.](https://github.com/axios/axios) + +### Other Ajax Syntax + +There is an older syntax for making an Ajax request that does not use `addEventListener`. It's much clunkier to write, but you might see it in the wild, so it's worth being familiar with. + +```javascript +let xhttp = new XMLHttpRequest(); +xhttp.onreadystatechange = function() { + if (this.readyState === 4 && this.status === 200) { + console.log(this.responseText); + } +}; +xhttp.open("GET", 'https://api.iify.org/?format=json'); +xhttp.send(); +``` + +The possible values for `readyState` are shown below, you can find more info [here](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState) +- 0 UNSENT: the request has not yet been sent +- 1 OPENED: the connection has been opened +- 2 HEADERS_RECEIVED: the response headers have been received +- 3 LOADING: the response body is loading +- 4 DONE: the request has been completed + +Here I've wrapped an AJAX request in a function to make it easier to use: + +```javascript +function http_get(url, success) { + let xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState === 4 && this.status === 200) { + let data = JSON.parse(xhttp.responseText); + success(data); + } + }; + xhttp.open("GET", url); + xhttp.send(); +} + +http_get("https://api.ipify.org/?format=json", function(data) { + console.log(data); +}); +``` + +jQuery also has it's own syntax for making an Ajax request: + +```javascript +$.ajax({ + method: "GET", // specify the HTTP Verb + url: 'https://api.iify.org/?format=json' // specify the URL +}).done(function(data) { + console.log(data); // log the data we get in response +}).fail(function() { + alert("error"); // indicate that an error has occurred +}); +``` + +### CORS + +CORS stands for 'cross-origin resources sharing', and represents a relaxation of the [same origin policy](https://en.wikipedia.org/wiki/Same-origin_policy). You can read more about CORS on the [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). + +If you receive the response "No 'Access-Control-Allow-Origin' header is present on the requested resource", it's because the remote server you're sending to the request from has security controls in place that prevent access from a client. You can read more about CORS [here](https://stackoverflow.com/questions/43871637/no-access-control-allow-origin-header-is-present-on-the-requested-resource-whe) and [here](https://security.stackexchange.com/questions/108835/how-does-cors-prevent-xss). + + +### JSONP + +JSONP (short for "JSON with Padding") is an additional security feature some APIs offer or require. You can read more about JSONP [here](https://stackoverflow.com/questions/3839966/can-anyone-explain-what-jsonp-is-in-layman-terms) and [here](https://stackoverflow.com/questions/16097763/jsonp-callback-function). + +### Public APIs + +- [a list on github](https://github.com/toddmotto/public-apis) +- [list on data.gov](https://catalog.data.gov/dataset?q=-aapi+api+OR++res_format%3Aapi#topic=developers_navigation) \ No newline at end of file diff --git a/3 JavaScript/docs/Vanilla JS vs Vue.md b/3 JavaScript/docs/Vanilla JS vs Vue.md new file mode 100644 index 00000000..828ecf42 --- /dev/null +++ b/3 JavaScript/docs/Vanilla JS vs Vue.md @@ -0,0 +1,201 @@ + + +# Vanilla JS vs Vue + +- [Setting Inner Text](#setting-inner-text) +- [Setting Inner HTML](#setting-inner-html) +- [Setting Attributes](#setting-attributes) +- [Handling Events](#handling-events) +- [Getting Input](#getting-input) +- [Generating Elements](#generating-elements) + + +## Setting Inner Text + +Both of these result in `
hello world!
` + +**Vanilla** +```html +
+ +``` + +**Vue** +```html +
+
{{message}}
+
+ +``` + +## Setting Inner HTML + +Both of these result in `
hello world!
` + +**Vanilla** +```html +
+ +``` + +**Vue** +```html +
+
+
+ +``` + +## Setting Attributes + +Both of these result in `
hover over me
` + + +**Vanilla** +```html +
hover over me
+ +``` + +**Vue** +```html +
+
hover over me
+
+ +``` + + +## Handling Events + +Both these result in a button that alerts 'hello world!'. + +**Vanilla** +```html + + +``` + +**Vue** +```html +
+ +
+ +``` + +## Getting Input + +Both these result in a button that alerts whatever text typed into the text field. + +**Vanilla** +```html + + + +``` + +**Vue** +```html +
+ + +
+ +``` + +## Generating Elements + + +**Vanilla** +```html + + +``` + +**Vue** +```html +
+
    +
  • {{fruit}}
  • +
+
+ +``` diff --git a/3 JavaScript/labs/Lab 04 Vue Todos.md b/3 JavaScript/labs/Lab 04 Vue Todos.md new file mode 100644 index 00000000..c9c91f3e --- /dev/null +++ b/3 JavaScript/labs/Lab 04 Vue Todos.md @@ -0,0 +1,70 @@ +# Lab 4: Vue Todos + +Use Vue to create a simple todo list in the browser. + +Your Vue app will need to do a few things: + * Store an array of objects (the todos themselves) + * List each todo + * Allow the user to add and remove todos + * Allow a user to toggle if a task is complete or not + + Reference the Vue.js [Introduction Guide](https://vuejs.org/v2/guide/). + +## How to get started + +* Create a simple local HTML project with the following structure: +``` +todos +├── css +│   └── site.css +├── index.html +└── js + └── site.js` +``` + +## How to serve your application + +* Install this plug-in for VSCode: `https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer` +* Make sure VSCode is open to the same folder as your `index.html` +* On the bottom bar in VSCode you should see an icon that says `Go Live` +* Click it and you're serving up a local version of your html file. It also auto-reloads everytime you make a change, neat right? + +## How to structure your `index.html` + +Example: `index.html` +```html + + + + + + + My Todo List + + + + +
+ {{ message }} +
+ + + + + + + +``` + +### How to start a simple Vue app: +```js +new Vue({ + el: '#app', + data: { + message: 'Hello world!' + } +}) +``` + +**Now if you check your browser you should see the message `Hello world!`** +**That means everything is working** diff --git a/3 JavaScript/labs/Lab 05 Quotes in Vue.md b/3 JavaScript/labs/Lab 05 Quotes in Vue.md new file mode 100644 index 00000000..1293d304 --- /dev/null +++ b/3 JavaScript/labs/Lab 05 Quotes in Vue.md @@ -0,0 +1,22 @@ +# Lab 5: FavQs in Vue + + +Build a Vue application that allows a user to search for quotes on FavQs. + +Requirements: +- Your app must use Vue to fetch data and interact with the results. +- Let the user enter a search term and select whether to search by keyword, author, or tag. +- Implement pagination buttons when the search returns more than 25 quotes. +- When the page first loads, show the user a set of completely random quotes. +- You must have at least one Vue component in your app. + +Resources: +- [FavQs API](https://favqs.com/api) +- [Vue documentation](https://vuejs.org/v2/guide/) +- [Using Axios to Consume APIs](https://vuejs.org/v2/cookbook/using-axios-to-consume-apis.html) +- [Axios documentation](https://github.com/axios/axios) + +Hints: +- Read the API documentation! +- Remember to use your Vue app `data` as your single source of truth. +- You'll need to set the `Authorization` header for the FavQs API to work. \ No newline at end of file diff --git a/3 JavaScript/labs/Lab 06 Mini-Capstone.md b/3 JavaScript/labs/Lab 06 Mini-Capstone.md new file mode 100644 index 00000000..6ebe5614 --- /dev/null +++ b/3 JavaScript/labs/Lab 06 Mini-Capstone.md @@ -0,0 +1,27 @@ +# Lab 6: Mini-Capstone: Any API in Vue + +Choose any API and write a page to interact with it. Be warned: some have particular security restrictions, and some return results in XML (which makes data more difficult to parse). + +Your page should take in some sort of user input and return different results based on that input, and your page should offer some sort of interactivity in the results. You should also apply some sort of basic professional styling -- using a CSS framework is totally fine. Your webpage should utilize Vue to build the interface and Axios for making ajax requests. + +https://vuejs.org/v2/cookbook/using-axios-to-consume-apis.html + +## Example APIs + +You can view long lists of public APIs [here](https://github.com/toddmotto/public-apis), [here](https://github.com/abhishekbanthia/Public-APIs), and [here](https://apilist.fun/). + +### User API + +There are several APIs to get fake user data which can be used to develop and test front-ends. Show a list of users with their attributes. You may also add a user detail page and pagination. + +- [randomuser.me](https://randomuser.me/documentation) +- [reques.in](https://reqres.in/) +- [jsonplaceholder](https://jsonplaceholder.typicode.com/) + +### Other APIs + +- Weather: [openweathermap.org](http://openweathermap.org/api) +- Number Facts: [numbersapi.com](http://numbersapi.com/#42) +- Photos: [flickr.com](https://www.flickr.com/services/api/) (requires key) +- Astronomical Data: [api.nasa.gov](https://api.nasa.gov/#live_example) +- USA Population Data: [datausa.io](https://datausa.io/about/api/) diff --git a/4 Django/__init__.py b/4 Django/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/4 Django/docs/01 Django Overview.md b/4 Django/docs/01 Django Overview.md new file mode 100644 index 00000000..14b0e4d1 --- /dev/null +++ b/4 Django/docs/01 Django Overview.md @@ -0,0 +1,112 @@ + +# Django Overview + +- [Overview](#overview) +- [Management Commands](#management-commands) + - [Custom Management Commands](#custom-management-commands) +- [Resources](#resources) + - [General](#general) + - [Tutorials](#tutorials) + - [Videos](#videos) + - [Forums](#forums) + - [Libraries](#libraries) + - [Tools](#tools) + + + +## Overview + +Django is a back-end framework written in Python. Django is a **high-level framework** meaning that it provides a great deal of functionality for you, but you have to connect the pieces together. You have to learn things the 'django way'. This also means that isn't necessarily any deeper intuition behind things, the only answer may be "that's just how Django does things". + +For comparison, [Flask](http://flask.pocoo.org/) is also a Python-based back-end framework, but whereas Django is high-level, Flask is low-level, meaning you're only given the most barebones functionality and have to do everything else yourself. Again, it's a balance of convenience and control. + +The core of Django is the [request-response cycle](django_diagram.png). A request is received by the server, it follows a **route**, actives a **view**, which then uses **models** and a **template** to generate a **response**, which is then rendered in the user's browser. The following docs will cover each of these topics in turn, but bear in mind that they're all interdependent. + +- Route: a mapping between a URL and a view +- View: a Python function which receives a request (url) and creates a response (html+css+js) +- Template: an HTML file with special syntax for filling in data +- Model: a Python class that parallels a database table + +Django applications are contained in a **project** which can have multiple **apps**. Each app has its own routes, views, templates, and models. How you divide up the functionality of the application is up to your discretion, what's important is that it makes sense to you. + + + +## Management Commands + +Management commands are executed in a terminal to perform operations on a django project. You can view a full list of the management commands [here](https://docs.djangoproject.com/en/3.2/ref/django-admin/) + +| Command | Description | +| --- | --- | +| `django-admin startproject myproject` | create a Django project | +| `python manage.py startapp myapp` | create an app | +| `python manage.py runserver` | run the server | +| `python manage.py makemigrations` | stage changes to the database | +| `python manage.py migrate` | apply changes to the database | +| `python manage.py createsuperuser` | create an admin (which has access to the admin panel) | +| `python manage.py collectstatic` | collects static files from each app and puts them into one folder, used for deployment | +| `python manage.py shell` | open an interactive session, often used to do database operations | + +### Custom Management Commands + +If you need to execute some Python code to perform administrative operations (load data into a database from a file or API, erase saved files, etc), you can write a custom management command. These are executed just like other management commands (`runserver`, `startapp`, `migrate`, etc). To create a custom management command, first create a `management` folder inside your app. Inside of that, create a `commands` folder. Inside of that, create a `mycommand.py`. \ + +- myproject + - myproject + - myapp + - management + - commands + - mycommand.py + + +Inside your `mycommand.py`, write the following. + +```python +from django.core.management.base import BaseCommand + +class Command(BaseCommand): + + def handle(self, *args, **options): + # write the code here + print('hello!') +``` + +Now you can execute this function using `python manage.py `. Any parameters you write after the `` will be passed to the `handle` function. + + + +## Resources + +### General + +- [MDN](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django) +- [Wikipedia](https://en.wikipedia.org/wiki/Django_(web_framework)) +- [Official Docs](https://docs.djangoproject.com/en/3.2/) + - [Table of Contents](https://docs.djangoproject.com/en/3.2/contents/) + +### Tutorials + +- [Official Tutorial](https://docs.djangoproject.com/en/3.2/intro/tutorial01/) +- [Django Girls Tutorial](https://tutorial.djangogirls.org/en/) +- [Real Python](https://realpython.com/tutorials/django/) +- [Simple is Better Than Complex](https://simpleisbetterthancomplex.com/archive/) + +### Videos + +- [Corey Schafer's Video Series](https://www.youtube.com/watch?v=UmljXZIypDc&list=PL-osiE80TeTtoQCKZ03TU5fNfx2UY6U4p) +- [The Net Ninja's Video Series](https://www.youtube.com/watch?v=n-FTlQ7Djqc&list=PL4cUxeGkcC9ib4HsrXEYpQnTOTZE1x0uc) +- [The Django Book](https://djangobook.com/beginning-django-tutorial-contents/) +- [Traversey Media](https://www.youtube.com/watch?v=e1IyzVyrLSU) +- [Dennis Ivy](https://www.youtube.com/watch?v=4RWFvXDUmjo) + +### Forums + +- [Official Forum](https://forum.djangoproject.com/) + +### Libraries + +- [Django Packages](https://djangopackages.org/) +- [A List](https://vsupalov.com/favorite-django-packages-2019/) + +### Tools + +- [SQLite Browser](http://sqlitebrowser.org/) diff --git a/4 Django/docs/02 Routes.md b/4 Django/docs/02 Routes.md new file mode 100644 index 00000000..99e765c2 --- /dev/null +++ b/4 Django/docs/02 Routes.md @@ -0,0 +1,78 @@ + +# Routes + +- [Overview](#overview) +- [Connecting the Project's `urls.py` to the App's `urls.py`](#connecting-the-projects-urlspy-to-the-apps-urlspy) +- [Connecting an App's `urls.py` to a View](#connecting-an-apps-urlspy-to-a-view) +- [Reverse URL Lookup](#reverse-url-lookup) +- [Parameters in the Path](#parameters-in-the-path) + + +## Overview + +Routes connect the **path** part of a URL to a **view**. The routes are stored in a `urls.py` file, which can be found in both the project folder and in each of the apps' folders. Routes are evaluated **in order**: whichever route matches first is the one used. You can visualize Django's routing system as a series of pipes, first the incoming request hits the project's `urls.py`, which the directs it to one of the app's `urls.py`, which then directs it to a view. You can read more about routes [here](https://docs.djangoproject.com/en/3.2/topics/http/urls/) and [here](https://docs.djangoproject.com/en/3.2/ref/urls/). + + +## Connecting the Project's `urls.py` to the App's `urls.py` + +The `include` function allows you to combine the routes of multiple `urls.py` files into one. This is used to connect the project's 'main' `urls.py` to the `urls.py` in each of the apps. + +**myproject/urls.py** +```python +from django.urls import path, include +from django.contrib import admin + +urlpatterns = [ + # route to admin panel + path('admin/', admin.site.urls), + # all routes in 'myapp/urls.py' will be under localhost:8000/mypath/... + path('mypath/', include('myapp.urls')) +] +``` + +## Connecting an App's `urls.py` to a View + +**myapp/urls.py** +```python +from django.urls import path + +from . import views + +app_name = 'myapp' +urlpatterns = [ + # localhost:8000/mypath/ + path('', views.index, name='index'), + # localhost:8000/mypath/about/ + path('about/', views.about, name='about'), +] +``` + +**myapp/views.py** +```python +from django.http import HttpResponse + +def index(request): + return HttpResponse('you are at the index') + +def about(request) + return HttpResponse('you are at the ) +``` + +## Reverse URL Lookup + +The `app_name` in the `urlspy` and the `name=` in each of the path are used to perform a reverse url lookup: [04 Templates - Reverse URL Lookup](04%20-%20Templates.md#reverse-url-lookup). + +**myapp/urls.py** +```python +from django.urls import path + +app_name = 'myapp' # <------------- +urlpatterns = [ + path('', views.index, name='index'), # <---------- +] +``` + +## Parameters in the Path + +You can specify a parameter in your path using ``, where `type` is the data type of the parameter (e.g. `str`, `int`, etc). See the [views.md](03%20-%20Views.md#path-parameters) file. + diff --git a/4 Django/docs/03 Views.md b/4 Django/docs/03 Views.md new file mode 100644 index 00000000..61a87234 --- /dev/null +++ b/4 Django/docs/03 Views.md @@ -0,0 +1,224 @@ + +# Views + + + +- [Overview](#overview) +- [Requests](#requests) + - [The Request Object](#the-request-object) + - [Path Parameters](#path-parameters) + - [Receiving Query Parameters](#receiving-query-parameters) + - [Receiving a Form Submission](#receiving-a-form-submission) + - [Receiving JSON](#receiving-json) +- [Responses](#responses) + - [Responding with a String / Raw HTML](#responding-with-a-string--raw-html) + - [Responding with a Template](#responding-with-a-template) + - [Responding with JSON](#responding-with-json) + - [Redirecting](#redirecting) + + +## Overview + +**Views** are python functions that do the bulk of the work, they receive the incoming request and return a response. The view can then respond with HTML, JSON, text, etc. An app's views are contained in its `views.py` file. You can read more about views [here](https://docs.djangoproject.com/en/3.2/topics/http/views/) and [here](https://docs.djangoproject.com/en/3.2/ref/request-response/). + +```python +from django.http import HttpResponse +def index(request): + return HttpResponse('hello world!') +``` + +## Requests + +### The Request Object + +The request object received by the view contains lots of important information. + +- `request.method` tells you which of the HTTP methods was used (GET, POST, etc) +- `request.body` the raw request body, you can also use `request.read()` +- `request.path` path of the requested page, e.g. `"/music/bands/beatles/"` +- `request.GET` dictionary of query parameters +- `request.POST` dictionary of form parameters +- `request.COOKIES` a dictionary of cookies + + +### Path Parameters + +You can specify parameters in the path using a datatype (`int`, `str`) and a name. Those values will then be automatically taken out of the path and passed as parameters to the view function. A common use for these is for a detail view for a record, with the primary key of the record specified in the path. + +**urls.py** +```python +from django.urls import path +from . import views +app_name = 'todoapp' +urlpatterns = [ + # e.g. /detail/5/, /detail/23/ + path('detail//', views.detail, name='detail') +] +``` + +**views.py** +```python +from django.shortcuts import get_object_or_404 +from .models import TodoItem +def detail(request, todo_item_id): + # look up the TodoItem with the given id + todo_item = get_object_or_404(TodoItem, pk=todo_item_id) + # pass that todo item to the template to be rendered + return render(request, 'todoapp/detail.html', {'todo_item': todo_item}) +``` + + +### Receiving Query Parameters + +Query parameters are passed as part of the url and are turned into dictionary-like objects. For example, if the path entered is `/mypath/?myvar=mytext`, we can retrieve the query parameters by name. + +```python +def view(request): + print(request.GET['myvar']) # 'mytext' +``` + +### Receiving a Form Submission + +When a form is submitted to a view, the data in the form is arranged into a dictionary. The `name` attributes of the input elements become the `keys` and the values the user enters into the input elements become the `values`. The view can then use the key to get the values out of the dictionary. For more about forms, check out [Templates - Forms](04%20-%20Templates.md#forms). + + +**myapp/templates/myapp/mytemplate.html** +```html +
+ + +
+``` + +**myapp/urls.py** +```python +from django.urls import path + +app_name = 'myapp' +urlpatterns = [ + path('mypath/', views.myview, name='mypathname') +] +``` + +**myapp/views.py** +```python +from django.http import HttpResponse +def myview(request): + print(request.POST['myname']) + return HttpResponse('ok') +``` + + +### Receiving JSON + +To read JSON data sent via AJAX, you can use the built-in `json` module to read the request's body. This turns the JSON into a Python dictionary. + + +```javascript +axios({ + method: 'post', + url: '{% url 'myapp:mypathname' %}', + data: {foo: 'bar', hello: 'world'} +}).then(function(response) { + console.log(response.data) +}) +``` + +```python +import json +def postdata(request): + data = json.loads(request.body) + print(data) + return HttpResponse('ok') +``` + + +## Responses + +### Responding with a String / Raw HTML + +```python +from django.http import HttpResponse + +def index(request): + return HttpResponse('Hello World!') + +def fruits(request): + fruits = ['apples', 'bananas', 'plums'] + html = '
    ' + for fruit in fruits: + html += '
  • ' + fruit + '
  • ' + html += '
' + return HttpResponse(html) +``` + +### Responding with a Template + +To render a template, use the `render` function. The first parameter is the original request, the second is the location of the template, and the third is a dictionary containing the variables to be rendered. + +```python +from django.shortcuts import render +from .models import TodoItem +def index(request): + todo_items = TodoItem.objects.all() + context = {'todo_items': todo_items} + return render(request, 'todos/index.html', context) +``` + +### Responding with JSON + +To respond with JSON, you can just pass a dictionary to a `JsonResponse`. + +**myapp/urls.py** +```python +from django.http import JsonResponse +def myview(request): + data = {'foo': 'bar', 'hello': 'world'} + return JsonResponse(data) +``` + +You can then send an HTTP request to this view via ajax. + +**myapp/templates/myapp/index.html** +```javascript +axios{ + method: 'get', + path: "{% url 'myapp:myview' %}" +}).then(function(response) { + console.log(response.data) +}) +``` + + +### Redirecting + +To redirect, you can use the HttpResponseRedirect class. You can redirect to a full url `http://mysite.com/` or if you put a path `/mypath/`, django will add it to the current domain `http://localhost:8000/mypath/`. This can cause issues (`reverse('google.com')` will redirect to `localhost:8000/google.com`. + +```python +from django.http import HttpResponseRedirect +def myview(request): + ... + return HttpResponseRedirect('/mypath/') +``` +```python +def myview(request): + ... + return HttpResponseRedirect('http://mysite.com/') +``` + +It's also best to use the [reverse](https://docs.djangoproject.com/en/3.2/ref/urlresolvers/#reverse) function to look up the url using the name rather than hard-coding it. This does the same reverse url redirect as the template: [04 Template - Reverse URL Lookup](04%20-%20Templates.md#reverse-url-lookup) + +```python +from django.http import HttpResponseRedirect +from django.urls import reverse +def add(request): + return HttpResponseRedirect(reverse('myapp:myview')) +``` + +You can also use the [redirect](https://docs.djangoproject.com/en/3.2/topics/http/shortcuts/#redirect) function. The difference is explained [here](https://stackoverflow.com/questions/13304149/what-the-difference-between-using-django-redirect-and-httpresponseredirect). + +```python +from django.shortcuts import redirect +def redirect(request): + return redirect('http://www.mozilla.org/') +``` diff --git a/4 Django/docs/04 Templates.md b/4 Django/docs/04 Templates.md new file mode 100644 index 00000000..dfd5f441 --- /dev/null +++ b/4 Django/docs/04 Templates.md @@ -0,0 +1,173 @@ +# Templates + +- [Overview](#overview) +- [Passing a Value to the Template](#passing-a-value-to-the-template) +- [Template Rendering Syntax](#template-rendering-syntax) + - [Rendering a Value](#rendering-a-value) + - [Conditionals](#conditionals) + - [Looping](#looping) +- [Reverse URL Lookup](#reverse-url-lookup) +- [Static Files](#static-files) +- [Template Inheritance: `block` and `extend`](#template-inheritance-block-and-extend) +- [Filters](#filters) + +## Overview + +Templates are like blueprints for your HTML pages. They contain plain HTML/CSS/JavaScript, but also additional syntax for generating HTML/CSS/JavaScript using variables from your Python view. You can read more about Templates [here](https://docs.djangoproject.com/en/3.2/topics/templates/) and [here](https://docs.djangoproject.com/en/3.2/ref/templates/builtins/) + + +## Passing a Value to the Template + +The variable names referred to inside the template must be defined in the data context (a dictionary) passed to the `render` function inside the view. + + + + +## Template Rendering Syntax + +### Rendering a Value + +You can render a value in a template using `{{}}`. + + +**views.py** +```python +from django.shortcuts import render +def index(self) + return render(request, 'myapp/index.html', {'name': 'Jane'}) +``` +**index.html** +```html +Hello, {{name}}! +``` + +### Conditionals + +What you put inside an `if` block will only be rendered if the condition is true. + +**views.py** +```python +from django.shortcuts import render +def index(self) + return render(request, 'myapp/index.html', {'temperature': random.randint(50, 100)}) +``` +**index.html** +```html +{% if temperature < 60 %} +cold +{% elif temperature < 80 %} +warm +{% else %} +hot +{% endif %} +``` + +### Looping + +Whatever you put inside the `for` block will be repeated for each iteration of the loop. For example, we can build a list of items. + +**views.py** +```python +from django.shortcuts import render +def index(self) + return render(request, 'myapp/index.html', {'fruits': ['apples', 'bananas', 'pears']}) +``` +**index.html** +```html +
    + {% for fruit in fruits %} +
  • {{ fruit }}
  • + {% endfor %} +
+``` + + +## Reverse URL Lookup + +In order for Django to find the proper path when rendering the template, the app's `urls.py` must contain the variable `app_name`, e.g. `app_name = 'todos'`. The `name` given in `urls.py` and the actual `path` can be different. To keep things simple, use consistent names. + + +**urls.py** +```python +from django.urls import path +from . import views +app_name = 'todos' +urlpatterns = [ + path('', views.index, name='index'), + path('add/', views.add, name='add') +] +``` + +**index.html** +```html +
+ {% csrf_token %} + + +
+``` + + +## Static Files + +To load static files into a page, create a folder in your app called `static`. Inside that folder, create a folder with the same name as your app (just as you did with templates). In your template, you then must add `{% load static %}` before you load your static file. + +- [Managing Static Files](https://docs.djangoproject.com/en/3.2/howto/static-files/) +- [Polls Tutorial: Part 6](https://docs.djangoproject.com/en/3.2/intro/tutorial06/) + +```html +{% load static %} +My image +``` + +## Template Inheritance: `block` and `extend` + +You can have one template 'inherit' from another, meaning the child template's content will be included inside the parent. You can accomplish this by putting a `{% block content %} / {% endblock %}` in the parent and an `{% extends '/.html' %}` in the child. This is useful if your header/footer/menus are consistent across multiple pages and you don't want to repeat the HTML. You can read more about template inheritance [here](https://tutorial.djangogirls.org/en/template_extending/). + +In the example below, `base.html` contains the header and footer. Two pages, `index.html` and `detail.html` inherit from `base.html`. The contents of each `{% block %}` in the child templates are used to fill the corresponding block in the parent when the template is rendered. + + +**base.html** +```html + + + Document + + +
+

{% block title %}{% endblock %}

+
+
+ {% block content %} + {% endblock %} +
+
+ © me 2020 +
+ + +``` + +**index.html** +```html +{% extends 'myapp/base.html' %} +{% block title %}Home{% endblock %} +{% block content %} +

this is the page content for the index page

+{% endblock %} +``` + +**detail.html** +````html +{% extends 'myapp/base.html' %} +{% block title %}Details{% endblock %} +{% block content %} +

this is content for the detail page

+{% endblock %} +```` + +## Filters + +Filters allow you to change how values are rendered in the template. + + diff --git a/4 Django/docs/05 Forms.md b/4 Django/docs/05 Forms.md new file mode 100644 index 00000000..fc17ba48 --- /dev/null +++ b/4 Django/docs/05 Forms.md @@ -0,0 +1,149 @@ + + +# Forms + +- [Overview](#overview) +- [Django Forms](#django-forms) + - [The ModelForm Class](#the-modelform-class) + - [Using Forms with CSS Frameworks](#using-forms-with-css-frameworks) + + + +## Overview + +A `form` is an HTML element that can transmit data from the front-end (client) to the back-end (server). Read more about forms [here](../../2%20Flask%20+%20HTML%20+%20CSS/docs/11%20HTML%20Forms.md). There are 5 important parts to a form: + +1. The `action` is the path or url to which the form's data will be submitted. +2. The `method` is the HTTP method to send the request in (POST, GET). +3. The `input` elements inside a form need name attributes, which will be used to retreive the data on the back-end. +4. The ` + +``` + +Django will take the key-value pairs from the form data in the request and put them into a dictionary-like object `request.POST`. You can then access those values from the view using the value of the `name` attribute as a key. + + +```python +def save_contact(request): # a view for receiving a form submission + print(request.POST) # verify we received the form data + first_name = request.POST['first_name'] # get the value the user entered into the 'first name' field + last_name = request.POST['last_name'] # get the value the user entered into the 'last name' field + contact = Contact(first_name=first_name, last_name=last_name) # create an instance of our model + contact.save() # save a new row to the database + ... +``` + +## Django Forms + +Django has a special Form class to make the creation of forms easier. These also do input validation on the front-end and the back-end for you. You can read in the official docs: [Working with Forms](https://docs.djangoproject.com/en/3.2/topics/forms/), [Forms API](https://docs.djangoproject.com/en/3.2/ref/forms/api/#django.forms.Form). You can put your forms in a `forms.py` inside your app. + + +**forms.py** +```python +from django import forms +class ContactForm(forms.Form): + contact_name = forms.CharField(label='Contact Name', max_length=100) + contact_age = forms.IntegerField(label='Contact Age') +``` + +**views.py** +```python +from django.shortcuts import render +from django.http import HttpResponseRedirect +from .forms import ContactForm +def index(request): + if request.method == 'POST': # receiving a form submission + # create an instance of our form from the form data + form = ContactForm(request.POST) + if form.is_valid(): + # get the data out of the form + contact_name = form.cleaned_data['contact_name'] + contact_age = form.cleaned_data['contact_age'] + # create an instance of our model from the data + contact = Contact(name=contact_name, age=contact_age) + # save a new record to the database + contact.save() + # create a new blank form for the template + form = ContactForm() + # if the form is invalid, we just send it back to the template + else: # receiving a GET request + form = ContactForm() # create a new blank form + return render(request, 'contacts/index.html', {'form': form}) # pass the form to the template +``` + +**index.html** +```html +
+ {% csrf_token %} + {{ form }} + +
+``` + + +### The ModelForm Class + +ModelForms allow us to generate a form directly from a model. You can read more about ModelForms in the [official docs](https://docs.djangoproject.com/en/3.2/topics/forms/modelforms/). + +**models.py** +```python +from django.db import models +class Contact(models.Model): + name = models.CharField(max_length=100) + age = models.IntegerField() +``` + +**forms.py** +```python +from django.forms import ModelForm +from .models import Contact +class TodoForm(ModelForm): + class Meta: + # the model to associate with the form + model = Contact + # a list of all the models' fields you want in the form + # fields = ['text'] + # or just use all of them + fields = '__all__' +``` + +**views.py** +```python +from django.shortcuts import render +from django.http import HttpResponseRedirect +from .forms import ContactForm +def index(request): + if request.method == 'POST': # receiving a form submission + form = ContactForm(request.POST) + if form.is_valid(): + form.save() # save the todo item associated with the form + form = ContactForm() # create a new blank form + # if the form is invalid, we just send it back to the template + else: # receiving a GET request + form = ContactForm() # create a new blank form + return render(request, 'contacts/index.html', {'form': form}) # pass the form to the template +``` + +**index.html** +```html +
+ {% csrf_token %} + {{ form }} + +
+``` + +### Using Forms with CSS Frameworks + +CSS Frameworks like Boostrap and Materialize have specific ways their forms are structures, and don't work with the default forms very well. [Crispy forms](https://django-crispy-forms.readthedocs.io/en/latest/) allow you to better control how forms are rendered. + +- [tutorial on using bootstrap w/ django forms](https://simpleisbetterthancomplex.com/tutorial/2018/08/13/how-to-use-bootstrap-4-forms-with-django.html) +- [library for using materialize w/ django forms](https://pypi.org/project/crispy-forms-materialize/) diff --git a/4 Django/docs/06 Models.md b/4 Django/docs/06 Models.md new file mode 100644 index 00000000..9eee02fd --- /dev/null +++ b/4 Django/docs/06 Models.md @@ -0,0 +1,533 @@ + + +# Models + +- [Overview](#overview) +- [Field Types](#field-types) + - [Blankable Fields](#blankable-fields) + - [Nullable Fields](#nullable-fields) + - [Default Values](#default-values) +- [Database Relationships](#database-relationships) + - [Many-to-One](#many-to-one) + - [One-to-One](#one-to-one) + - [Many-to-Many](#many-to-many) + - [The On-Delete Parameter: `on_delete`](#the-on-delete-parameter-on_delete) + - [The Related-Name Parameter: `related_name`](#the-related-name-parameter-related_name) +- [ORM Operations](#orm-operations) + - [Example Models and Data](#example-models-and-data) + - [Creating a Record](#creating-a-record) + - [Getting a Record](#getting-a-record) + - [Updating a Record](#updating-a-record) + - [Get All Rows](#get-all-rows) + - [Check if a Record Exists](#check-if-a-record-exists) + - [Filter Rows](#filter-rows) + - [Specify an Order](#specify-an-order) + - [Specify the Number of Records to Return](#specify-the-number-of-records-to-return) + - [Get the Number of Records](#get-the-number-of-records) + - [Advanced ORM](#advanced-orm) + + +## Overview + +Models are Python classes that parallel tables in the database. The ORM (object-relational mapping) manages this dual representation, translating statements in Python to queries on the database. You can read more about models [here](https://docs.djangoproject.com/en/3.2/topics/db/models/), and more about the ORM [here](https://docs.djangoproject.com/en/3.2/ref/models/querysets/). For ORM practice, check out the [Polls Tutorial - Part 2](https://docs.djangoproject.com/en/3.2/intro/tutorial02/). + +Database tables are like spreadsheets: they have headers and rows. Tables can also be thought of as Python classes, where the headers are class attributes, and the rows are class instances. All models are automatically given an `id` field as a primary key, which is used to uniquely identifies a row. + +| id | email_address | first_name | last_name | +| --- | --- | --- | --- | +| 1 | wendy@gmail.com | Wendy | Carson | +| 2 | alyssa@gmail.com | Alyssa | Lyons | +| 3 | brian@gmail.com | Brian | Barber | + +```python +from django.db import models + +# our contact model +class Contact(models.Model): + email_address = models.CharField(max_length=200) + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + +# get the record with id=1 from the database, the first row +contact = Contact.objects.get(id=1) +# access the column value as a class attribute +print(contact.first_name) # Wendy + +# create a new instance of our model, creating the third row +contact_new = Contact(first_name='Brian', last_name='Barber', email='brian@gmail.com') +contact_new.save() # save it to the database +``` + +## Field Types + +The fields of a model create represent both the attribute of a class and the column of a table. You can read more about the field types [here](https://docs.djangoproject.com/en/3.2/ref/models/fields/). Below are some of the common fields used with a model. + +- `BooleanField` represents a boolean (true/false) value +- `IntegerField` represents an integer +- `FloatField` represents a floating-point number +- `CharField` represents a string, requires `max_length` parameter indicating the number of characters +- `TextField` like `CharField` but has unlimited length +- `DateTimeField` represents a datetime (more [here](https://docs.djangoproject.com/en/3.2/topics/i18n/timezones/)) +- `OneToOneField` represents a [one-to-one relationship](https://docs.djangoproject.com/en/3.2/topics/db/examples/one_to_one/) +- `ForeignKey` represents a [many-to-one relationship](https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_one/) +- `ManyToManyField` represents a [many-to-many relationship](https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/) + + +### Blankable Fields + +Fields that are marked `blank=True` allow the user to insert a blank value in the admin panel, which will result in a blank string for a `CharField` or `TextField`, or `null` for other field types. + +```python +from django.db import models +class Contact(models.Model): + ... + favorite_color = models.CharField(blank=True) # the user can save a blank string in the admin panel +``` + + +### Nullable Fields + +Fields that are marked `null=True` are 'nullable', meaning they can have a null value. In Python, the attributes of the model will have a value of `None`. To save records with `null` value from the admin panel, you must also add `blank=True`. [more info] (https://www.geeksforgeeks.org/nulltrue-django-built-in-field-validation/) + +```Python +from django.db import models + +class TodoItem(models.Model) + ... + date_completed = models.DateTimeField(null=True, blank=True) +``` + +### Default Values + +You can specify a default value for a field by adding `default=value`. That way, you can leave the value out when creating and saving an instance. + +```python +from django.db import models + +class BlogPost(models.Model): + text = models.CharField() + upvotes = models.IntegerField(default=0) +``` + +```python +blog_post = BlogPost(text="Lorem Ipsum") # no need to specify age, it will default to 0 +blog_post.save() + +blog_post2 = BlogPost(text="Delorum Est", upvotes=3) # we can specify if needed +blog_post2.save() +``` + + +## Database Relationships + +The three types of database relationships: one-to-one, many-to-one, and many-to-many. The `id` field of a table is called the **primary key** because it uniquely identifies a row. When another table contains a reference to that `id` field, it's called a **foreign key**. + +### Many-to-One + +A many-to-one relationship means that for every row in table A, there may be multiple rows in table B connected to it. An example might be between a [mother and her children](https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/CPT-Databases-OnetoMany.svg/460px-CPT-Databases-OnetoMany.svg.png). A mother may have multiple children, but any child only has one mother. You can read more about many-to-one relationships [here](https://docs.djangoproject.com/en/2.1/topics/db/examples/many_to_one/). + +In the following example, `city_id` on `Contact` is a **foreign key**, `id` on `Contact` and `id` on `City` are **Primary Keys**. This is an example of a **many-to-one relationship**. + +**Contacts** +| id | first_name | last_name | email_address | city_id | +| --- | --- | --- | --- | --- | +| 1 | Wendy | Carson | wendy@gmail.com | 1 | +| 2 | Alyssa | Lyons | alyssa@gmail.com | 1 | +| 3 | Brian | Barber | brian@gmail.com | 2 | + +**Cities** +| id | name | +| --- | --- | +| 1 | Portland | +| 2 | Eugene | + + +```python +from django.db import models + +class City(models.Model): + name = models.CharField(max_length=200) + + def __str__(self): + return self.name + +class Contact(models.Model): + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + email_address = models.CharField(max_length=200) + city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='contacts') + + def __str__(self): + return self.first_name + ' ' + self.last_name +``` + +Notice the `related_name` on the `ForeignKey` field. This controls how we refer to the list of all the `Contact`s for a given `City`. By default Django will take model name, make it lowercase, and add an `_set`, so if we did not specify `related_name` here it would be `contact_set`, but because we did, it will be `contacts`. + +```python +contact = Contact.objects.get(first_name='Alyssa') +# only one city per contact +print(contact.city.name) # Portland + +city = City.objects.get(name='Portland') +# multiple contacts for a given city +contacts = city.contacts.all() +print(contacts) # Wendy, Alyssa +``` + + +### One-to-One + +A one-to-one relationship means that for every row in table A, there will be a single corresponding row in table B. An example might be between [counties and capital cities](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f7/CPT-Databases-OnetoOne.svg/460px-CPT-Databases-OnetoOne.svg.png). A country only has one capital. A capital only pretains to one country. You can read more about one-to-one relationships [here](https://docs.djangoproject.com/en/3.2/topics/db/examples/one_to_one/). Normally a one-to-one relationship is unnecessary, because one could just take the fields from both models and put them onto one model. But you may have to associate new fields with an old model without changing the old model, or need to restrict access to certain data [more info](https://stackoverflow.com/questions/25206447/when-to-use-one-to-one-relationships-in-django-models). + + + +**Capital** +| id | name | +| --- | --- | +| 1 | Washington DC | +| 2 | Mexico City | +| 3 | Ottawa | + + +**Country** +| id | name | +| --- | --- | +| 1 | The United States | +| 2 | Mexico | +| 3 | Canada | + + +```python +from django.db import models + +class Capital(models.Model): + name = models.CharField(max_length=200) + + def __str__(self): + return self.name + +class Country(models.Model): + name = models.CharField(max_length=200) + capital = models.OneToOneField(Capital, on_delete=models.CASCADE, related_name='country') + + def __str__(self): + return self.name +``` + +```python + +capital = Capital.objects.get(name='Washington DC') +print(capital.country.name) # The United States + +country = Country.objects.get(name='The United States') +print(country.capital.name) # Washington DC + +# create a new capital +capital_city = Capital(name='Canberra') +capital_city.save() + +# create a new city +country = Country(name='Australia', capital=capital_city) +country.save() +``` + +### Many-to-Many + +An example of many-to-many relationships might be between [authors and books](https://upload.wikimedia.org/wikipedia/commons/thumb/c/c4/CPT-Databases-ManytoMany.svg/460px-CPT-Databases-ManytoMany.svg.png). One book may have multiple authors. One author may have multiple books. A many-to-many relationship can be created in Django using a [ManyToManyField](https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/). To maintain such a relationship in SQL, Django creates a [junction table](https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/Databases-ManyToManyWJunction.jpg/800px-Databases-ManyToManyWJunction.jpg) with two many-to-one relationships. + + + +**Books** +| id | title | +| --- | --- | +| 1 | Good Omens | +| 2 | The Odyssey | +| 3 | The Illiad | + + +**Authors** + +| id | name | +| --- | --- | +| 1 | Homer | +| 2 | Terry Pratchett | +| 3 | Neil Gaiman | + + +**Book-Authors** + +| id | book_id | author_id | +| --- | --- | --- | +| 1 | 2 | 1 | +| 2 | 1 | 2 | +| 3 | 1 | 3 | + + + +```python +from django.db import models + +class Book(models.Model): + title = models.CharField(max_length=50) + + def __str__(self): + return self.name + +class Author(models.Model): + name = models.CharField(max_length=200) + books = models.ManyToManyField(Book, related_name='authors') + + def __str__(self): + return self.title +``` + + +```python +book = Book.objects.get(title='Good Omens') +authors = book.authors.all() # all the authors for a book +print(authors) # Terry Pratchett, Neil Gaiman + +author = Author.objects.get(name='Homer') +books = author.objects.all() # all the books for an author +print(books) # The Odyssey, The Illiad +``` + + +### The On-Delete Parameter: `on_delete` + +The `on_delete` parameter lets you control what to do with other rows when a connected row is deleted. You can read more about `on_delete` [here](https://docs.djangoproject.com/en/3.2/ref/models/fields/#arguments). The important options are: + +- `CASCADE` deleted this row when the other is deleted +- `PROTECT` throws an exception when the other is deleted, this forces the developer re-assign the relationship when they want to delete a row +- `SET_NULL` sets the field containing the relationship to null (the field must also be nullable) +- `SET_DEFAULT` sets the field containing the relationship to its default value (a default must be specified) + +For example, consider the following models and data: + +**Contacts** +| id | first_name | last_name | email_address | city_id | +| --- | --- | --- | --- | --- | +| 1 | Wendy | Carson | wendy@gmail.com | 1 | +| 2 | Alyssa | Lyons | alyssa@gmail.com | 1 | +| 3 | Brian | Barber | brian@gmail.com | 2 | + +**Cities** +| id | name | +| --- | --- | +| 1 | Portland | +| 2 | Eugene | + + +```python +from django.db import models + +class City(models.Model): + name = models.CharField(max_length=200) + + def __str__(self): + return self.name + +class Contact(models.Model): + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + email_address = models.CharField(max_length=200) + city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='contacts') + + def __str__(self): + return self.first_name + ' ' + self.last_name +``` + +Note the `on_delete=models.CASCADE`, this means if we delete a `City`, all the `Contact`s associated with that `City` will automatically be deleted as well. + +```python +city = City.objects.get(name='Portland') +city.delete() # also deletes the Contacts Wendy and Alyssa +``` + +### The Related-Name Parameter: `related_name` + +The `related_name` parameter controls what the name of the related class's attribute is. For example, consider the following models and data: + +**Contact** +| id | first_name | last_name | email_address | city_id | +| --- | --- | --- | --- | --- | +| 1 | Wendy | Carson | wendy@gmail.com | 1 | +| 2 | Alyssa | Lyons | alyssa@gmail.com | 1 | +| 3 | Brian | Barber | brian@gmail.com | 2 | + +**Cities** +| id | name | +| --- | --- | +| 1 | Portland | +| 2 | Eugene | + + +```python +from django.db import models + +class City(models.Model): + name = models.CharField(max_length=200) + + def __str__(self): + return self.name + +class Contact(models.Model): + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + email_address = models.CharField(max_length=200) + city = models.ForeignKey(City, on_delete=models.CASCADE, related_name='contacts') + + def __str__(self): + return self.first_name + ' ' + self.last_name +``` + +Notice the `related_name` on the `ForeignKey` field. This controls how we refer to the list of all the `Contact`s for a given `City`. By default Django will take model name, make it lowercase, and add an `_set`, so if we did not specify `related_name` here it would be `contact_set`, but because we did, it will be `contacts`. + +```python +contact = Contact.objects.get(first_name='Alyssa') +# only one city per contact +print(contact.city.name) # Portland + +city = City.objects.get(name='Portland') +# multiple contacts for a given city +contacts = city.contacts.all() +print(contacts) # Wendy, Alyssa +``` + + +## ORM Operations + +The ORM 'object relational mapping' provides functions in Python that perform operations on the database. To read more about ORM operations, look [here](https://docs.djangoproject.com/en/3.2/topics/db/queries/). Note that `__init__`, `get`, and `filter` take `**kwargs` (which turns named parameters into a dictionary), whereas `order_by` takes `*args` (which turns arguments into a list). + +### Example Models and Data + +**Contact** +| id | first_name | last_name | email_address | city_id | +| --- | --- | --- | --- | --- | +| 1 | Wendy | Carson | wendy@gmail.com | 1 | +| 2 | Alyssa | Lyons | alyssa@gmail.com | 1 | +| 3 | Brian | Barber | brian@gmail.com | 2 | +| 2 | Wendy | Clark | alyssa@gmail.com | 1 | + +**Cities** +| id | name | +| --- | --- | +| 1 | Portland | +| 2 | Eugene | + + +```python +from django.db import models + +class Contact(models.Model): + first_name = models.CharField(max_length=200) + last_name = models.CharField(max_length=200) + + def __str__(self): + return self.first_name + ' ' + self.last_name +``` + + +### Creating a Record + +We create an instance of our model by invoking the class's initializer, which takes `kwargs`. + +```python +contact = Contact(first_name='Alena', last_name='Deacon') +contact.save() +``` + +### Getting a Record + +We can get a particular row using `get()` and passing the values of one or more attributes. + +```python +# get a record using a single attribute +contact = Contact.objects.get(id=1) +print(contact) # Wendy Carson + +# get a record using two attributes +contact = Contact.objects.get(first_name='Wendy', last_name='Carson') +print(contect) # Wendy Carson +``` + +An exception will occur if a record is not found. A safer way is to use `filter` (which gives us a list of matches) and `first` (which gives us the first result if there are any, and `None` if there isn't). + +```python +contact = Contact.objects.filter(first_name='Alena').first() +print(contact) # None +``` + + +### Updating a Record + +We can update a record by assigning values to its attributes and saving. + +```python +contact = Contact.objects.get(id=1) +contact.first_name = 'Cindy' +contact.save() +``` + + +### Get All Rows + +To get all the rows in a table use `all()`. + +```python +contacts = Contact.objects.all() +print(contacts) # Wendy Carson, Alyssa Lyons, Brian Barber +``` + +### Check if a Record Exists + +We can use `filter` (which gives us a list of matches) and `exists` (which return `True` if there are records, and `False` if there aren't). + + +```python +if Contact.objects.filter(first_name='Cindy').exists(): + ... +``` + +### Filter Rows + +We can use `filter()` to get a list of records. + +```python +contacts = Contact.objects.filter(first_name='Wendy') +print(contacts) # Wendy Carson, Wendy Clark +``` + +### Specify an Order + +To specify an order, use `order_by`, which takes any number of strings containing the names of the fields to sort by. By default sort is ascending, use a negative symbol `-` to sort in the descending order. + +```python +# sort by last name in ascending order +# then first name in descending order +contacts = Contact.objects.order_by('last_name', '-first_name') +``` + + +### Specify the Number of Records to Return + +To limit the number of items returned, use slicing. + +```python +# only get the first 5 results +contacts = Contact.objects.all()[:5] +``` + +### Get the Number of Records + + +```python +contacts = Contact.objects.all().count() +``` + +### Advanced ORM + +To filter variables by whether or not a field is null, use `__isnull` +```python +completed_items = TodoItem.objects.filter(date_completed__isnull=False) +``` \ No newline at end of file diff --git a/4 Django/docs/07 User Management.md b/4 Django/docs/07 User Management.md new file mode 100644 index 00000000..13e6c57c --- /dev/null +++ b/4 Django/docs/07 User Management.md @@ -0,0 +1,242 @@ + +# User Management + +- [Overview](#overview) +- [Users, Groups, and Permissions](#users-groups-and-permissions) +- [Creating & Editing Users](#creating--editing-users) +- [Changing Passwords](#changing-passwords) +- [Authentication, Login, & Logout](#authentication-login--logout) +- [Authorization](#authorization) + - [is_authenticated](#is_authenticated) + - [has_perm](#has_perm) + - [@login_required](#login_required) + - [@permission_required](#permission_required) + - [@user_passes_test(f)](#user_passes_testf) +- [Extending the User Model](#extending-the-user-model) +- [Managing Groups and Permissions](#managing-groups-and-permissions) + + +## Overview + +Many web applications have the ability for a user to 1) create an account, 2) log into and out of that account, and 3) view pages that are only accessible to logged-in users. For more info, read [here](https://docs.djangoproject.com/en/3.2/topics/auth/) and [here](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Authentication). + + +## Users, Groups, and Permissions + +Start by looking at the users section of the admin interface. Here you can create users, groups, and assign permissions. A group is a collection of users which you can add and remove permisions from, so you don't have to go to each user to change their permissions. Django has many built-in permissions, but you can also define your own. For more information about these, look [here](https://docs.djangoproject.com/en/3.2/ref/contrib/auth/). + +## Creating & Editing Users + +You can create users programmatically using the 'create_user' function, which automatically creates a user and saves it. It's important to note that Django does not save passwords in 'plain text', only a hash of the password. This means you cannot retrieve a user's password, only check if the password you have is correct by putting it through the same hashing algorithm. You can read more about how Django manages passwords [here](https://docs.djangoproject.com/en/3.2/topics/auth/passwords/). + +```python +from django.contrib.auth.models import User +user = User.objects.create_user('jane', 'jane@gmail.com', 'janespassword') +``` + +You can also create users from within the admin panel, by clicking 'add' next to 'Users' under 'AUTHENTICATION AND AUTHORIZATION'. + + +## Changing Passwords + +You can change a user's password using `set_password` in Python or `changepassword` in the terminal. You can also change a user's password in the admin panel. + +```python +from django.contrib.auth.models import User +user = User.objects.get(username='jane') +user.set_password('newpassword') +user.save() +``` + +``` +python manage.py changepassword jane +``` + +## Authentication, Login, & Logout + +To log a user in, we can use a form to post the username and password to a view. The `authenticate` function verifies their username and password are correct. If they are, it returns the user. If they aren't, it returns `None`. After verifying that the username and password match, we can log a user in using `login`. + +```python +from django.contrib.auth import authenticate, login + +def mylogin(request): + # retrieve the variables from the form submission + username = request.POST['username'] + password = request.POST['password'] + user = authenticate(request, username=username, password=password) + if user is not None: + login(request, user) + # redirect to a success page + else: + # return an 'invalid login' error message +``` + +Logging out a user is even simpler. + +```python +from django.contrib.auth import logout + +def logout_view(request): + logout(request) + # redirect to a success page. +``` + +## Authorization + +### is_authenticated + +In other views, we can check if a user is logged in by checking the `is_authenticated` field. + +```python +def otherview(request): + if request.user.is_authenticated: + # do something for authenticated users. + else: + # do something else for anonymous users. +``` +### has_perm + +If you want to restrict access to users with particular permissions, use `has_perm`. + +```python +def otherview(request): + if request.user.has_perm('blog.add_comment'): + # do something for users with this permision + else: + # do something for everyone else +``` + +### @login_required + +Django comes with a built-in decorator which can check if a user is logged in. If the user is logged in, the execution of the view coninues unabated. If not, the user will be redirected to [settings.LOGIN_URL](https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-LOGIN_URL). You can read more [here](https://docs.djangoproject.com/en/3.2/topics/auth/default/#the-login-required-decorator). + +```python +from django.contrib.auth.decorators import login_required + +@login_required +def my_view(request): + ... +``` + +### @permission_required + +Like `@login_required`, if this fails, the user will be redirected to [settings.LOGIN_URL](https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-LOGIN_URL). +You can read more [here](https://docs.djangoproject.com/en/3.2/topics/auth/default/#the-permission-required-decorator). + +```python +from django.contrib.auth.decorators import permission_required + +@permission_required('polls.can_vote') +def my_view(request): + ... +``` + +### @user_passes_test(f) + +The `@users_passes_test` decorator takes a function which is given a user. That function can then return `True` or `False` whether that user should be allowed in. Like the others, if this fails, the user will be redirected to [settings.LOGIN_URL](https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-LOGIN_URL). You can read more [here](https://docs.djangoproject.com/en/3.2/topics/auth/default/#limiting-access-to-logged-in-users-that-pass-a-test). + + +```python +from django.contrib.auth.decorators import user_passes_test + +def email_check(user): + return user.email.endswith('@example.com') + +@user_passes_test(email_check) +def my_view(request): + ... +``` + + +## Extending the User Model + +The built-in user model only has a few fields (username, email, first name, last name). If you'd like to associate more information with the user (phone number, location, profile image), there are two main strategies. + +### Inherit from AbstractUser + +We can create a custom user model by inheriting from `AbstractUser`. You should create one **when you start a project**. It's much more difficult to change once you already have users in your database. You can read more [here](https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#auth-custom-user). + + +**models.py** +```python +from django.contrib.auth.models import AbstractUser + +class User(AbstractUser): + phone_number = models.CharField(max_length=20) + ... +``` + +**settings.py** +```python +AUTH_USER_MODEL = 'myapp.User' +``` + +**admin.py** +```python +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import User + +admin.site.register(User, UserAdmin) +``` + + + +### Separate UserProfile Model + +Another option is to have a separate model with a one-to-one field connected to the built-in user model. + +```python +from django.contrib.auth.models import User + +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.PROTECT, related_name='user_profile') + phone_number = models.CharField(max_length=20) + ... +``` + +The caveat of this approach is that accessing that information via the ORM takes an extra step, which is a little messy. + +```python + +def index(request): + print(request.user.user_profile.phone_number) +``` + + +## Managing Groups and Permissions + +The `User` model has two many-to-many fields: groups and permissions. You can access these on the User object using the ORM. Note that `user_permissions` only include permissions assigned to that individual user, and not permissions that user has as part of a group. However, `has_perm` will check if the given permission is amony the group. + +- `user.groups.set([group_list])` set the groups +- `user.groups.add(group, group, ...)` add to a group +- `user.groups.remove(group, group, ...)` remove from group +- `user.groups.clear()` remove from all groups +- `user.user_permissions.set([permission_list])` set permissions +- `user.user_permissions.add(permission, permission, ...)` add permissions +- `user.user_permissions.remove(permission, permission, ...)` remove permissions +- `user.user_permissions.clear()` clear all user permissions +- `user.has_perm(permission_code)` check if a user has a permission, either in user_permissions or in one of their groups + +```python +from django.contrib.auth.models import User, Group, Permission + +# add a user to a group +group = Group.objects.get(name='commenters') +user.groups.add(group) +user.save() + +# add a permission to a group +permission = Permission.objects.get(codename='change_comment') +group.permissions.add(permission) +group.save() + +# check if a user has a permission +if user.has_perm('blog.add_comment'): + # ... + + +# check if a user is in a group +if user.groups.filter(name='commenters').exists(): + # ... +``` \ No newline at end of file diff --git a/4 Django/docs/08 Uploading Files.md b/4 Django/docs/08 Uploading Files.md new file mode 100644 index 00000000..9c5610d7 --- /dev/null +++ b/4 Django/docs/08 Uploading Files.md @@ -0,0 +1,90 @@ + + +# Media Files + +- [Overview](#overview) +- [1: Specify the Save Location](#1-specify-the-save-location) +- [2: Set up the Model](#2-set-up-the-model) +- [3: Add a Route to Access the Files](#3-add-a-route-to-access-the-files) +- [4: Test](#4-test) +- [5: Render an Image](#5-render-an-image) +- [6: Put a Form on your Page](#6-put-a-form-on-your-page) +- [7: Add a View to Receive the Form and Save the Model](#7-add-a-view-to-receive-the-form-and-save-the-model) + + +## Overview + +Web applications often allow users to upload files. This document covers how to allow users to upload files and save them alongside our application on a server. If you're using cloud hosting, you may want to look at alternative ways of storing files which separate the files from the application. Look at the official docs for more info: [File Uploads](https://docs.djangoproject.com/en/3.2/topics/http/file-uploads/), [ImageField](https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.ImageField), [FileField](https://docs.djangoproject.com/en/3.2/ref/models/fields/#django.db.models.FileField). You may also look at the different [mime types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types). + + +## 1: Specify the Save Location + +In your project's `settings.py`, set the following variables. + +```python +MEDIA_URL = '/uploaded_files/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'uploaded_files') +``` + +## 2: Set up the Model + +Given the settings shown here, files will be saved to `/uploaded_files/images`. + +```python +from django.db import models +class MyModel(models.Model): + my_image = models.ImageField(upload_to='images/') +``` + +## 3: Add a Route to Access the Files + +In your project's `urls.py`, add the following line at the bottom. This will give the user the ability to access the file statically. Note that there's built-in access restriction, so anyone with a valid link will be able to view and download the associated file. + +```python +from django.urls import path +from django.conf import settings +from django.conf.urls.static import static +urlpatterns = [ + ... +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) +``` + +## 4: Test + +At this point, it's best to register your model with the admin panel, go to your admin panel, upload a file, make sure that the file appears in the directory you expected and that the link to the file works. + +## 5: Render an Image + +Once you have a few instances of your model saved, you can use the `url` property on the `ImageField` to render the image inside the template or create a link to it. + +```html + + + + +{{image.name}} +``` + +## 6: Put a Form on your Page + +If we want to let users upload images, we can create a form with `input` `type="file"`. Notice the `enctype` on the `form`. + +```html +
+ + + +
+``` + +## 7: Add a View to Receive the Form and Save the Model + +```python +from .models import MyModel +def upload_image(request): + my_image = request.FILES['my_image'] + model = MyModel(..., my_image=my_image) + model.save() +``` + + diff --git a/4 Django/docs/09 Advanced Topics.md b/4 Django/docs/09 Advanced Topics.md new file mode 100644 index 00000000..766f8f62 --- /dev/null +++ b/4 Django/docs/09 Advanced Topics.md @@ -0,0 +1,13 @@ + +# Advanced Django + +You can view a list of topics [here](https://docs.djangoproject.com/en/3.2/topics/). + +- [Django Rest Framework](http://www.django-rest-framework.org/#example) +- [Forms](https://docs.djangoproject.com/en/3.2/topics/forms/) +- [SSL](https://docs.djangoproject.com/en/1.11/topics/security/#ssl-https) +- [File Uploads](https://docs.djangoproject.com/en/1.11/topics/http/file-uploads/) +- [Email](https://docs.djangoproject.com/en/1.11/topics/email/) +- [Pagination](https://docs.djangoproject.com/en/1.11/topics/pagination/) +- [Class-based Views](https://docs.djangoproject.com/en/1.11/topics/class-based-views/) ([list of build-in class-based views](https://docs.djangoproject.com/en/1.11/ref/class-based-views/)) +- [Testing](https://docs.djangoproject.com/en/1.11/topics/testing/) diff --git a/4 Django/docs/Class-Based Views.md b/4 Django/docs/Class-Based Views.md new file mode 100644 index 00000000..4d567991 --- /dev/null +++ b/4 Django/docs/Class-Based Views.md @@ -0,0 +1,157 @@ + + +# Class-Based Views + + +- [Overview](#overview) +- [Using Class-Based Views with Routing](#using-class-based-views-with-routing) +- [Base Views](#base-views) + - [View](#view) + - [TemplateView](#templateview) +- [Display Views](#display-views) + - [ListView](#listview) + - [DetailView](#detailview) +- [Editing Views](#editing-views) + + +## Overview + +The caveat with class-based views is that while they provide much for you, you must know how to customize them, they reflect the balance of convenience and control you find elsewhere in programming. + +- [Reference Documentation](https://docs.djangoproject.com/en/3.2/ref/class-based-views/) +- [Introduction to Class-Based Views](https://docs.djangoproject.com/en/3.2/topics/class-based-views/intro/) +- [Polls Tutorial: Part 4](https://docs.djangoproject.com/en/3.2/intro/tutorial04/#use-generic-views-less-code-is-better) +- [Built-in class-based generic views](https://docs.djangoproject.com/en/3.2/topics/class-based-views/generic-display/) +- [Form handling with class-based views](https://docs.djangoproject.com/en/3.2/topics/class-based-views/generic-editing/) +- [Using mixins with class-based views](https://docs.djangoproject.com/en/3.2/topics/class-based-views/mixins/) +- [simpleisbetterthancomplex.com's overview](https://simpleisbetterthancomplex.com/article/2017/03/21/class-based-views-vs-function-based-views.html) + + +## Using Class-Based Views with Routing + +There are two ways to use the built-in class-based views, either directly in the `urls.py` or by subclassing them. To create a route to a class-based view, you have to call `as_view()` and pass the result to `path`. The `DetailView` requires a `pk` in the path. + +```python +from django.urls import path +from . import views +app_name = 'myapp' +urlpatterns = [ + + # function-based view + path('', views.index, name='index'), + path('/', views.detail, name='detail'), + + # class-based view + path('', views.IndexView.as_view(), name='index'), + path('/', views.DetailView.as_view(), name='detail') +] +``` + +## Base Views + +- [Base Views: View, TemplateView, RedirectView](https://docs.djangoproject.com/en/3.2/ref/class-based-views/base/) + +### View + +This the the base class of all other class-based views. It allows you to write a method to handle each HTTP method. + +```python +# function-based view +def index(request): + if request.method == 'GET': + #... + elif request.method == 'POST': + #... + +# class-based view +from django.views import View +class MyView(View): + def get(self, request): + #... + def post(self, request): + #... +``` + +### TemplateView + +The `TemplateView` allows you to define a view just by specifying a template name and the data used to render it. + +```python +from django.views.generic.base import TemplateView +from .models import MyModel +class MyView(TemplateView): + template_name = "mytemplate.html" + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['my_model'] = MyModel.objects.all() + return context +``` + +You can actually use a TemplateView without writing your own implementation. + +```python +from django.views.generic.base import TemplateView +urlpatterns = [ + path('', TemplateView.as_view(template_name='index.html'), name="home"), +] +``` + + +## Display Views + +- [Display Views: DetailView,List View](https://docs.djangoproject.com/en/3.2/ref/class-based-views/generic-display/) + +### ListView + +A `ListView` + +The list of objects will default to the name `mymodel_list` in the template. You can customize this by setting `context_object_name` inside your view. You can also filter the data by overriding the `get_queryset` method. + +The template used will default to `mymodel_list.html`. You can customize this by specifying a `template_name` inside your view. + + +```python +from django.views import generic +from .models import MyModel +class MyListView(generic.ListView): + model = MyModel + + #below are default values you can change + #context_object_name = 'mymodel_list' + #template_name = 'myapp/mymodel_list.html' + #def get_queryset(self): + # return MyModel.objects.all() +``` + + + +### DetailView + +DetailView expects a 'pk' in the route: `path('/', views.DetailView.as_view(), name='detail')`. + +The object's name will default to the name `mymodel` in the template. You can customize this by setting `context_object_name` inside your view. + +The template used will default to `mymodel_detail.html`. You can customize this by specifying a `template_name` inside your view. + + +```python +from django.views import generic +from .models import MyModel +class MyDetailView(generic.DetailView): + model = MyModel + + #below are default values you can change + #context_object_name = 'mymodel' + #template_name = 'myapp/mymodel_detail.html' + #def get_object(self): + # object = super().get_object() + # return object +``` + + +## Editing Views + +- [Editing Views: FormView, CreateView, UpdateView, DeleteView](https://docs.djangoproject.com/en/3.2/ref/class-based-views/generic-editing/) + + + diff --git a/4 Django/docs/DRF and Vue.md b/4 Django/docs/DRF and Vue.md new file mode 100644 index 00000000..e551b58a --- /dev/null +++ b/4 Django/docs/DRF and Vue.md @@ -0,0 +1,156 @@ +# Django Rest Framework and Vue + +Once you have an API made in Django Rest Framework, it's easy to retrieve data using Vue. + +## Two ways to structure your project + +In most professional web applications, the front end and the back end are completely separate code bases. Often they are even in different repositories! This gives you the advantage of modular development. It's easy for one person or team to work on the front end while others work on the back end, with a common API interface between them. This also means you can swap out front-ends or the back-end serving your API as technologies come and go. It also makes it easy to have multiple front-ends, for example iOS/Android apps, a desktop app, a web app, etc. + +There is a downside though. Because the back-end and front-end are separate, authentication is a lot more work. In vanilla Django, cookies and user sessions are used to track when a user is logged in as they visit templates. By hosting our front-end separately, Django can't manage users for us. Instead, we need to use something called token authentication. When a user wants to log in to our app, they need to send an API request with their authentication details. If they are correct, DRF will return an authentication token to be submitted with all other API requests. + +For our purposes, we're going to bypass this requirement by serving up a hybrid approach to DRF and Vue. By embedding our Vue app in a Django template, we can take advantage of Django's user session system an we will not have to write any special authentication code beyond what we did in vanilla Django. This approach creates code that can sometimes get messy and inelegant whith a larger Vue application, but it saves us a lot of time and hassle. + +## Import Vue + +To use Vue in a Django template, simply make sure the end of your `body` has a `script` tag to load Vue. I recommend writing your Vue app in a `script` tag at the bottom of your template instead of an external JavaScript file. This means all your templates are in the same file. + +```django +{% extends 'base.html' %} + +{% block content %} + +
+

{{ message }}

# Uh oh, this is the same template syntax as Django! We'll need to fix that... +
+ + + +{% endblock %} +``` + +## Changing delimiters + +By default, both Django and Vue templates use `{{ }}` to insert expressions. We need to change the delimiters that Vue uses to square brackets instead. This can be done by adding a `delimiters` value to our root Vue configuration object. + +```django +{% extends 'base.html' %} + +{% block content %} + +
+

[[ message ]]

# Much better! +
+ + + +{% endblock %} +``` + +## Call your API + +If you have an API, you can use an `axios` ajax call in your `mounted` method to load the initial data when the page loads. + +This requires you to have an API to get the data from. You have two choices: create manual API endpoints by creating views that return a `JSONResponse` (see *02 - Views.md*), or use Django Rest Framework to quickly create a full API, and then call the endpoint that corresponds the data you would like to load. + +Once you have an API endpoint, your code is the same as any other API call. Make sure you either include the CSRF token in the template or set your API views to be `csrf_exempt`. + +```django +... + data: { + grocery_items: [], + new_grocery_item: { + name: "", + }, +... + mounted: { + let crsf_token = document.querySelector("input[name=csrfmiddlewaretoken]").value; + axios({ + url: '/api/grocery_item/', + method: 'get', + headers: { + 'X-CSRFToken': csrf_token + } + }).then(res => this.grocery_items = res.data) + } +... +``` + +## Sending information back to Django + +You'll need to send a data object back to your API. Axios makes this easy. Again, this can be a custom API endpoint you created (see *02 - Views.md*) or it can be a Django Rest Framework endpoint. + +```django +... + data: { + grocery_items: [ ... ], + new_grocery_item: { + name: " ... ", + }, +... + methods: + save: { + let crsf_token = document.querySelector("input[name=csrfmiddlewaretoken]").value; + axios({ + url: 'api/grocery_item/', + method: 'post', + headers: { + X-CSRFToken: csrf_token + }, + data: this.new_grocery_item + }).then(res => console.log(res)) + }, +... +``` + +This is a *create* example, hence the `method: 'post'`. To *edit*, use `method: 'put'` or `method: 'patch'`. To *delete*, use `method: 'delete'`. For more information, take a look at the DRF's browsable API to see what your endpoints and avaliable methods are. If you submit an invalid request, DRF is very good and letting you know why. You can either handle the error in your Axios promise, or simply open the browser development tools and open the `Network` tab to view the error message that DRF returned. + +If you're using Django REST Framework, that's it! If it didn't work, read your own API documentation and the DRF documentation to make sure you are submitting properly. + +If you're using a custom API endpoint that you wrote, make sure it connects to a view. You can read the JSON body of your incoming request using `request.body` and parsing back from JSON to a Python dictionary. + +#### views.py +```python +def save(request): + if request.method == 'POST': + json_data = json.loads(request.body) + try: + # access data here. you probably want to get an object, edit it, and save it. + except KeyError: + HttpResponseServerError("Malformed data!") + HttpResponse("Thumbs up, you did it!") +``` + +## Templates vs Vue + +Vue and Django templates fulfill the same role: presentation of data. With this in mind, decide which you are going to use to render your page. For instance, trying to use `{% for %}` and `v-for` in the same template will quickly make things complicated and buggy. For best results, I recommend using Vue to create pages and avoiding Django, even though technically you're editing a Django template. diff --git a/4 Django/docs/Deployment.md b/4 Django/docs/Deployment.md new file mode 100644 index 00000000..38e55044 --- /dev/null +++ b/4 Django/docs/Deployment.md @@ -0,0 +1,111 @@ + + +# Deployment + +- [Hosting Services](#hosting-services) + - [Examples](#examples) + - [SaaS vs PaaS vs IaaS](#saas-vs-paas-vs-iaas) + - [Domain Names](#domain-names) + - [DNS](#dns) + - [HTTPS](#https) +- [Deploying Django](#deploying-django) + - [Deploying with PythonAnywhere](#deploying-with-pythonanywhere) + - [Deploying with Heroku](#deploying-with-heroku) + - [Deploying with AWS](#deploying-with-aws) + - [Deploying with DigitalOcean](#deploying-with-digitalocean) + +## Hosting Services + +### Examples + +In order to have your web application accessible on the web, you'll have to find a hosting service. Below are some popular hosting services. + +- [PythonAnywhere](https://www.pythonanywhere.com/) +- [Heroku](https://devcenter.heroku.com/articles/deploying-python) +- [WebFaction](https://www.webfaction.com/) +- [NearlyFreeSpeech.net](https://www.nearlyfreespeech.net/) +- [Digital Ocean](https://www.digitalocean.com/) +- [Amazon Web Services (AWS)](https://aws.amazon.com/) +- [Microsoft Azure](https://azure.microsoft.com/en-us/) + +### SaaS vs PaaS vs IaaS + +SaaS, PaaS, and IaaS are three different types of hosting services. [more info](https://www.bmc.com/blogs/saas-vs-paas-vs-iaas-whats-the-difference-and-how-to-choose/) + +| | description | examples | +|--- |--- |--- | +| SaaS | Software as a Service: they provide nearly everything through an interface of their web application | Wordpress, Squarespace | +| PaaS | Platform as a Service: they provide the software and hardware, you copy files over and configure | PythonAnywhere, NearlyFreeSpeech, Windows Azure, AWS | +| IaaS | Infrastructure as a Service: they provide the hardware, you install software | Digital Ocean, AWS | +| Self-hosted | You manage all the software and hardware | | + + + +### Domain Names + +Most hosting services will give your website a default domain name, but if you want to use a custom domain name, you'll have to rent it from a domain registrar. You can then add a DNS record to associate it with your server's IP address. You can read more about domains on the [MDN](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_domain_name). + +- [Google Domains](https://domains.google/) +- [Hover](https://www.hover.com/) +- [GoDaddy](https://www.godaddy.com/) + +### DNS + +DNS stands for "[Domain Name System](https://en.wikipedia.org/wiki/Domain_Name_System)" and represents a series of servers which associate domain names with IP addresses. When you type a domain name into your browser's address bar and hit 'enter', the browser first queries the DNS to find out which IP address belongs to that domain name. Then it sends the request to that IP address and displays the response on the page. DNS servers can store different types of records. + +|Type|Description|Function| +|--- |--- |--- | +|A|Address record|Returns a 32-bit IPv4 address, most commonly used to map hostnames to an IP address of the host.| +|AAAA|IPv6 address record|Returns a 128-bit IPv6 address, most commonly used to map hostnames to an IP address of the host.| +|CNAME|Canonical name record|Alias of one name to another: the DNS lookup will continue by retrying the lookup with the new name.| +|TXT|Text record|Originally for arbitrary human-readable text in a DNS record, it's now often used to verify domain ownership for SSL.| + + + +### HTTPS + +To allow your site to use HTTP, you must install an SSL Certificate on your server. SSL certificates can be purchased from a Commercial Certificate Authority, created with Let's Encrypt, or self-signed. + + +## Deploying Django + +First check out the [Django deployment checklist](https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/) and working with [static files](https://docs.djangoproject.com/en/3.2/howto/static-files/deployment/), then consider these additional steps. + +- Set up a [virtual environment](../../1%20Python/docs/Virtual%20Environments.md) with a `requirements.txt` to keep track of your libraries. +- Create a `development_settings.py` and a `production_settings.py` to be able to switch configurations and hide your production environment's [SECRET_KEY](https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-SECRET_KEY). + +If you want to generate a new secret key you can use the code below: +```python +from django.core.management.utils import get_random_secret_key +print(get_random_secret_key()) +``` + +If your project uses Django Channels then deployment is [more complicated](https://channels.readthedocs.io/en/latest/deploying.html). + +### Deploying with PythonAnywhere + +- [Tutorial](https://help.pythonanywhere.com/pages/DeployExistingDjangoProject) ([video](https://www.youtube.com/watch?v=Y4c4ickks2A)) +- [Static Files](https://help.pythonanywhere.com/pages/DjangoStaticFiles) +- [Custom Domains](https://help.pythonanywhere.com/pages/CustomDomains/) +- [HTTPS](https://help.pythonanywhere.com/pages/HTTPSSetup/) +- [Help Pages](https://help.pythonanywhere.com/pages/) + +It can be difficult to debug on PythonAnywhere because you do not have access to the process running django and thus cannot see the output of `print()`. However, you can print to the error log with the code below, a link to the error log can be found on the web app's page. + +```python +import sys +print('check the error log', file=sys.stderr) +``` + +### Deploying with Heroku + +Check out [this tutorial](https://devcenter.heroku.com/articles/getting-started-with-python?singlepage=true) + +### Deploying with AWS + +Check out [this tutorial](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create-deploy-python-django.html) + +### Deploying with DigitalOcean + +Check out [this tutorial](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-18-04) + diff --git a/4 Django/docs/Django Project Setup.md b/4 Django/docs/Django Project Setup.md new file mode 100755 index 00000000..93fdcc80 --- /dev/null +++ b/4 Django/docs/Django Project Setup.md @@ -0,0 +1,243 @@ + +# How to start a Django project  (_PDXCG Style_) + +## Table of Contents + +1. [Setup the project](#setup) + - [Reference: "Am I inside or outside the virtual environment?"](#where-am-i) +1. [Create a Django App](#create-app) +1. [Create a View](#create-view) +1. [Create a Route to the View](#create-route) +1. [Running the web server locally](#runserver) +1. [Create Models](#create-models) +1. [Add the Model to the Admin Panel](#add-model) +1. [Create a Template](#create-template) +1. [Render a Template](#render-template) +1. [Set up template directories](#template-dirs) +1. [Set up static directories](#static-dirs) + +- ## [Quickstart (Abbreviated version)](#quickstart) + +--- +## Setup the project + +1. Create a directory. Open a terminal and navigate to a new (blank) folder, and create your project folder. Make sure to replace **$PROJECT_NAME** with your own project name. Your project name should be named in snake_case and contain no capital letters. + * `mkdir $PROJECT_NAME` +2. Change into that directory + * `cd $PROJECT_NAME` +3. Install django (requires `pipenv` to be installed via `pip install pipenv` . In case you ever need to remove the virtual environment, use `pipenv --rm`) + * `pipenv install django` +4. Get into the environment + * `pipenv shell` + * `pip list` (verify Django is installed) +5. Create your project in the currect directory + * `django-admin startproject $PROJECT_NAME .` + * **The period at the end of this command is important!** It says ignore creating a new folder and put the contents of our new project in the current directory. + + +### How to tell, "Where am I?" "Inside the virtual environment or not?" + +There are several ways to check whethe you are inside the virtual environment or not: + +* `pipenv shell` (You can't shell into the environment again if you're already in there. But do NOT created a doubly-nested virtual environment!) +* `pip list` (Should show a full list of installed python packages on your system, or a more limited list inside the virtual environment.) +* `which python` (Does not work on all OS/python/terminal configurations.) This should give two possibilities. `C/system/python` would mean you are outside in your main terminal, or something including `C/users/name/.virtualenvs/$PROJECT-NAME-{UNIQUE-PIPENV-CODE}/Scripts/python` Both the `.virtualenvs` and the `unique-project-name-with-code` indicate you are inside the virtual environment. + + + +--- +## Create a Django app inside the project + +Creating an app to add to your project is done by calling `python manage.py startapp $APPNAME_HERE` + +Django won't recognize your app until you append it to the `INSTALLED_APPS` list in `settings.py`. + +eg: +```python +INSTALLED_APPS = [ + ... other apps + '$APPNAME_HERE', +] +``` + +--- +## Create a View + +- In your app's `views.py`: +```python +from django.http import HttpResponse +def (request): + return HttpResponse('ok') +``` +A common `` is `index`. + +--- +## Create a Route to the View + +- Create a `urls.py` inside your app +- Add a route in your app's `urls.py` which points to the the view +- Add an `app_name` to be able to look up paths when you render a template + +```python +from django.urls import path +from . import views + +app_name = '' # for namespacing +urlpatterns = [ + path('', views., name='') +] +``` + +- Add a route in your project's `urls.py` which points to the app's `urls.py` using `include` + +```python +from django.urls import path, include +from django.contrib import admin + +urlpatterns = [ + path('admin/', admin.site.urls), + path('/', include('.urls')) # Note: all your app urls will start with this path +] +``` +--- +## Running the web server locally + +At this point, you should run the server (`python manage.py runserver`) and go to `localhost:8000/app_path/view_path` and verify that you can access the view. +The local Django web server will generally remain running and automatically restart if it detects changes in any .py files, but this is the first place you should check if you can't access it locally. Close the server like any python file with `Cmd+C / Control+C`. Remember to restart it after performing any direct commands to the manage.py file. + +--- +## Create Models + +- Define your models (Python classes) in the app's `models.py` +- Stage your migrations: `python manage.py makemigrations ` +- (optional) View the SQL commands that will occur during migrations: `python manage.py sqlmigrate `. You can find the migration number and the code that'll be executed during the migration in `/migrations/_initial.py` +- Perform migrations (synchronize your models with your database): `python manage.py migrate` + +--- +## Add the Model to the Admin Panel + +- Add a `def __str__(self):` to your model so the admin interface knows how to display it. +- Make your app visible in the admin panel by registering your models with our app's `admin.py` + ```py + admin.site.register(Model) + ``` + +```python +from django.contrib import admin +from .models import +admin.site.register() +admin.site.register() +``` + +- Go to `localhost:8000/admin` in your browser, and add some data. + +--- +## Create a Template + +- Create a folder inside your app called `templates`, inside of that create another folder with the name of your app, and inside of *that* create a `.html`. You can view examples of the template syntax [here](03%20-%20Templates.md). + +--- +## Render a Template + +- Inside your view, you can use the `render` shortcut to render a template. The first parameter is the request, the second parameter is the name of the template, and the third is a dictionary containing the values you'd like to render in the template. + +```python +from django.shortcuts import render +def (request): + context = {} + return render(request, '/