Merge remote-tracking branch 'pedro/pedro'

This commit is contained in:
Nico Schottelius 2020-07-22 16:35:00 +02:00
commit 668f99a4e5
25 changed files with 704 additions and 0 deletions

View file

@ -10,3 +10,4 @@ into the ungleich clearning circle.
* [Pedro](https://code.ungleich.ch/pedro/ungleich-learning-circle/-/tree/pedro): git@code.ungleich.ch:pedro/ungleich-learning-circle.git
* [Sami](https://code.ungleich.ch/samialazar/learning-cicle): git@code.ungleich.ch:samialazar/learning-cicle.git
* [Youngjin](https://code.ungleich.ch/youngjin.han/ungleich-learning-circle): git@code.ungleich.ch:youngjin.han/ungleich-learning-circle.git
* [Pedro](https://code.ungleich.ch/pedro/ungleich-learning-circle): git@code.ungleich.ch:pedro/ungleich-learning-circle.git

2
pedro/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*pyc
django-tutorial/mysite/db.sqlite3

View file

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,16 @@
"""
ASGI config for mysite project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_asgi_application()

View file

@ -0,0 +1,126 @@
"""
Django settings for mysite project.
Generated by 'django-admin startproject' using Django 3.0.8.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '37jgpv28n1x!f3-48h)4ad99&jz-ek$@e6uf*an5uve6%7atqq'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = [
"localhost",
"2a0a-e5c1-011f-0000-0000-0000-0000-0001.has-a.name"
]
# Application definition
INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'mysite.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'mysite.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Madrid'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'

View file

@ -0,0 +1,22 @@
"""mysite URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]

View file

@ -0,0 +1,16 @@
"""
WSGI config for mysite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_wsgi_application()

View file

@ -0,0 +1,26 @@
from django.contrib import admin
# Register your models here.
from .models import Choice, Question
# stacked: that's lot of space
#class ChoiceInline(admin.StackedInline):
class ChoiceInline(admin.TabularInline):
model = Choice
extra = 3
class QuestionAdmin(admin.ModelAdmin):
# # this way we changed the order
#fields = ['pub_date', 'question_text']
# # different organization of fields
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date']}),
]
inlines = [ChoiceInline]
list_display = ('question_text', 'pub_date', 'was_published_recently')
list_filter = ['pub_date']
search_fields = ['question_text']
admin.site.register(Question, QuestionAdmin)

View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class PollsConfig(AppConfig):
name = 'polls'

View file

@ -0,0 +1,32 @@
# Generated by Django 3.0.8 on 2020-07-03 12:48
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Question',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_text', models.CharField(max_length=200)),
('pub_date', models.DateTimeField(verbose_name='date published')),
],
),
migrations.CreateModel(
name='Choice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('choice_text', models.CharField(max_length=200)),
('votes', models.IntegerField(default=0)),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Question')),
],
),
]

View file

@ -0,0 +1,27 @@
import datetime
from django.db import models
from django.utils import timezone
# Create your models here.
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
# bug
#return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View file

@ -0,0 +1,10 @@
li a {
color: green;
}
body {
/* thanks: https://stackoverflow.com/questions/12605908/change-background-image-opacity/40366996#40366996 */
background-image: url("images/background.gif");
background-color: rgba(255,255,255,0.6);
background-blend-mode: lighten;
}

View file

@ -0,0 +1,12 @@
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>

View file

@ -0,0 +1,15 @@
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
{# <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> #}
{# refactor the hardcoded URL #}
{# <li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li> #}
{# use URL namespace #}
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}

View file

@ -0,0 +1,9 @@
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

View file

@ -0,0 +1,128 @@
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
from django.urls import reverse
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date
is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
def create_question(question_text, days):
"""
Create a question with the given `question_text` and published the
given number of `days` offset to now (negative for questions published
in the past, positive for questions that have yet to be published).
"""
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
"""
If no questions exist, an appropriate message is displayed.
"""
response = self.client.get(reverse('polls:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_past_question(self):
"""
Questions with a pub_date in the past are displayed on the
index page.
"""
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_future_question(self):
"""
Questions with a pub_date in the future aren't displayed on
the index page.
"""
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
are displayed.
"""
create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_two_past_questions(self):
"""
The questions index page may display multiple questions.
"""
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
"""
The detail view of a question with a pub_date in the future
returns a 404 not found.
"""
future_question = create_question(question_text='Future question.', days=5)
url = reverse('polls:detail', args=(future_question.id,))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_question(self):
"""
The detail view of a question with a pub_date in the past
displays the question's text.
"""
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)

View file

@ -0,0 +1,17 @@
from django.urls import path
from . import views
# namespacing URLs
app_name = 'polls'
urlpatterns = [
# ex: /polls/
#path('', views.index, name='index'),
path('', views.IndexView.as_view(), name='index'),
# ex: /polls/5/
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
# ex: /polls/5/results/
path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]

View file

@ -0,0 +1,125 @@
from django.shortcuts import get_object_or_404, render
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
from django.template import loader
from django.urls import reverse
from django.views import generic
from .models import Choice, Question
from django.http import Http404
from django.utils import timezone
## simple index
#def index(request):
# return HttpResponse("Hello, world. You're at the polls index.")
## meaningful index
#def index(request):
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# output = ', '.join([q.question_text for q in latest_question_list])
# return HttpResponse(output)
## meaningful and templated index
#def index(request):
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# template = loader.get_template('polls/index.html')
# context = {
# 'latest_question_list': latest_question_list,
# }
# return HttpResponse(template.render(context, request))
## meaningful, templated and easy to write index
## and then we no longer need to import loader and HttpResponse
#def index(request):
# latest_question_list = Question.objects.order_by('-pub_date')[:5]
# context = {
# 'latest_question_list': latest_question_list,
# }
# return render(request, 'polls/index.html', context)
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
## has bug
# def get_queryset(self):
# """Return the last five published questions."""
# return Question.objects.order_by('-pub_date')[:5]
# refactor
def get_queryset(self):
"""
Return the last five published questions (not including those set to be
published in the future).
"""
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
## simple detail
#def detail(request, question_id):
# return HttpResponse("You're looking at question %s." % question_id)
## meaningful detail
#def detail(request, question_id):
# try:
# question = Question.objects.get(pk=question_id)
# except Question.DoesNotExist:
# raise Http404("Question does not exist")
# return render(request, 'polls/detail.html', {'question': question})
## meaningful and easy to write detail
#def detail(request, question_id):
# question = get_object_or_404(Question, pk=question_id)
# return render(request, 'polls/detail.html', {'question': question})
## refactor detail view
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
"""
Excludes any questions that aren't published yet.
"""
return Question.objects.filter(pub_date__lte=timezone.now())
#def results(request, question_id):
# response = "You're looking at the results of question %s."
# return HttpResponse(response % question_id)
# # real implementation of results
#def results(request, question_id):
# question = get_object_or_404(Question, pk=question_id)
# return render(request, 'polls/results.html', {'question': question})
# # refactor results view
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
#def vote(request, question_id):
# return HttpResponse("You're voting on question %s." % question_id)
# #real implementation of vote
def vote (request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

View file

@ -0,0 +1,9 @@
{% extends "admin/base.html" %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">Polls
{% endblock %}
{% block nav-global %}{% endblock %}

View file

@ -0,0 +1,53 @@
import ipaddress
import argparse
# for exit codes
import sys
def proc_args():
# src https://docs.python.org/3/library/argparse.html#description
parser = argparse.ArgumentParser(description='Compare two IPs and check if they are overlapped or not')
parser.add_argument('ip1', type=str,
help='IPv6 number 1')
parser.add_argument('ip2', type=str,
help='IPv6 number 2')
args = parser.parse_args()
return args
# - Write a python script that checks whether two IPv6 networks are overlapping
# - *overlap.py ip1 ip2*
def parse_ip(ip_arg):
if('/' in ip_arg):
ip_arg = ip_arg.split('/')[0]
else:
# if no network specified, assume a netmask of /48 for all of them
ip_arg = ip_arg + '/48'
# strict false to avoid -> https://stackoverflow.com/questions/40839147/ipaddress-module-valueerrors-has-host-bits-set-self
# strict is passed to IPv4Network or IPv6Network constructor. A ValueError is
# raised if address does not represent a valid IPv4 or IPv6 address, or if
# the network has host bits set. -> src https://docs.python.org/3/library/ipaddress.html#ipaddress.ip_network
return ipaddress.IPv6Network(ip_arg, False)
def main():
is_overlap=False
args = proc_args()
try:
ip1 = parse_ip(args.ip1)
ip2 = parse_ip(args.ip2)
except:
print('incorrect IPv6 address')
# src https://docs.python.org/3/library/ipaddress.html#ipaddress.IPv4Network.overlaps
is_overlap = ip1.overlaps(ip2)
if(is_overlap):
print('overlap')
sys.exit(1)
else:
print('no overlap')
sys.exit(0)
if __name__ == "__main__":
main()

32
pedro/ipv6-stuff/overlap_run.sh Executable file
View file

@ -0,0 +1,32 @@
#!/bin/sh
run_program() {
echo "args:\n ip1: ${1}\n ip2: ${2}"
printf 'output: '
python3 overlap.py ${1} ${2}
echo "exit code: $?"
echo '----------------'
}
# - Use the following test IPv6 addresses:
# - 2001:db8::
# - 2001:db8:0:2::
# - 2001:db8:1::
# - Step 2: Make your script parse ipv6 networks (like
# 2001:db8::/48 and 2001:db8::/64) ) and check whether they overlap
ip1='2001:db8::'
ip2='2001:db8:0:2::'
run_program $ip1 $ip2
ip1='2001:db8::'
ip2='2001:db8:1::'
run_program $ip1 $ip2
ip1='2001:db8:0:2::'
ip2='2001:db8:1::'
run_program $ip1 $ip2
ip1='2001:db8::/48'
ip2='2001:db8::/64'
run_program $ip1 $ip2