Miguel Cantillana <mcantillana@linets.cl>
Docker es una plataforma que permite construir, ejecutar y compartir aplicaciones mediante contenedores. La idea base de docker es poder empaquetar aplicaciones que puedan ser compartidas mediante contenedores.
El web framework para perfeccionistas con deadline
Django pone énfasis en el re-uso, la conectividad y extensibilidad de componentes, el desarrollo rápido y el principio No te repitas
https://demo.pycon.e2l.dev/
https://github.com/mcantillana/django-pycon
version: '3'
services:
web:
image: mcantillana/django_pycon:latest
command: python manage.py runserver 0:8000
ports:
- "8000:8000"
volumes:
- .:/code
networks:
- net-pycon
volumes:
db_data:
networks:
net-pycon:
docker-compose run web django-admin startproject main .
├── docker-compose.yml
├── main
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
docker-compose run web python manage.py migrate
docker-compose up
docker-compose up -d
docker-compose run web python manage.py startapp catalog
├── catalog
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── db.sqlite3
├── docker-compose.yml
├── Dockerfile
├── main
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
INSTALLED_APPS = [
...
'catalog.apps.CatalogConfig',
]
class Product(models.Model):
"""
Clase que permite modelar un producto
"""
name = models.CharField(max_length=144)
image = models.ImageField(upload_to='catalog/')
sku = models.CharField(max_length=50, unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField(default=0)
short_description = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
status = models.BooleanField()
sort_order = models.PositiveIntegerField(default=0)
categories = models.ManyToManyField(Category)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Category(models.Model):
"""
Modelo de categorias
"""
name = models.CharField(max_length=144)
status = models.BooleanField(default=True)
sort_order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
## creamos una migración del módelo
docker-compose exec web python manage.py makemigrations
## Aplicamos una migración a la DB
docker-compose exec web python manage.py migrate
docker-compose exec web python manage.py createsuperuser
<url base>:<port>/admin
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'status')
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = (
'name',
'sku',
'price',
'quantity',
'status',
'sort_order',
'image_tag'
)
def image_tag(self,obj):
tag_image = '<img src="{0}" style="width: 45px; height:45px;" />'.format(
obj.image.url)
return format_html(tag_image)
list_filter = ('status', 'categories')
search_fields = ('sku', 'name')
from django.shortcuts import render
def category(request, category_id):
template_name = 'category.html'
data = {}
return render(request, template_name, data)
from django.urls import path
from django.urls.conf import include
from catalog import views
urlpatterns = [
path('category/<int:category_id>', views.category),
]
from django.urls import path
from django.urls.conf import include
from catalog import views
urlpatterns = [
path('category/<int:category_id>', views.category),
path('category/<int:category_id>/product/<int:product_id>', views.product),
]
from django.shortcuts import render
from catalog.models import Category, Product
def category(request, category_id):
template_name = 'category.html'
data = {}
data['category'] = Category.objects.get(pk=category_id)
data['products'] = Product.objects.filter(categories__id=category_id)
return render(request, template_name, data)
def product(request, category_id, product_id):
template_name = 'product.html'
data = {}
data['category'] = Category.objects.get(pk=category_id)
data['product'] = Product.objects.get(pk=product_id)
return render(request, template_name, data)
...
<div class="row">
<div class="col mb-2">
<h2 class="page-title">{{category.name}}</h2>
</div>
<div class="row">
{% for product in products %}
<div class="col-md-3 col-6 mb-4">
<div class="card" >
<a href="{% url 'catalog:product' category.id product.id %}">
<img src="{{product.image.url}}" class="card-img-top" alt="{{product.name}}">
</a>
<div class="card-body">
<h5 class="card-title">{{product.name}}</h5>
<p class="card-text">{{product.sku}}</p>
<a href="{% url 'catalog:product' category.id product.id %}" class="btn btn-primary">Comprar</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
....
def category(request, category_id):
template_name = 'category.html'
data = {}
page = request.GET.get('page', 1)
data['category'] = Category.objects.get(pk=category_id)
products = Product.objects.filter(categories__id=category_id).order_by('sort_order')
paginator = Paginator(products, 1) # Show 25 contacts per page.
try:
data['products'] = paginator.page(page)
except PageNotAnInteger:
data['products'] = paginator.page(1)
except EmptyPage:
data['products'] = paginator.page(paginator.num_pages)
return render(request, template_name, data)
{% if products.has_other_pages %}
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-end">
{% if products.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ product.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">«</a>
</li>
{% endif %}
{% for page in products.paginator.page_range %}
{% if products.number == page %}
<li class="page-item active"><a class="page-link" href="?page={{ page }}">{{ page }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ page }}">{{ page }}</a></li>
{% endif %}
{% endfor %}
{% if products.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ products.next_page_number }}" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">»</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
docker-compose exec web python manage.py startapp cms
INSTALLED_APPS = [
...
'cms.apps.CmsConfig',
]
from django.urls import path
from cms import views
app_name = 'cms'
urlpatterns = [
path('', views.home, name='home'),
]
urlpatterns = [
...
path('', include('cms.urls')),
]
{% extends "base.html" %}
{% block main %}
<main class="mt-4">
<div class="container">
<div class="row">
<div id="carouselExampleIndicators" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Slide 1"></button>
<button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="1" aria-label="Slide 2"></button>
<button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="2" aria-label="Slide 3"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="https://via.placeholder.com/1500x600" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="https://via.placeholder.com/1500x600" class="d-block w-100" alt="...">
</div>
<div class="carousel-item">
<img src="https://via.placeholder.com/1500x600" class="d-block w-100" alt="...">
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<h3>Oportunidades</h3>
</div>
<div class="col-md-3 col-6 mb-3"><a href=""><img src="https://via.placeholder.com/500x500" class="img-fluid" alt=""></a></div>
<div class="col-md-3 col-6 mb-3"><a href=""><img src="https://via.placeholder.com/500x500" class="img-fluid" alt=""></a></div>
<div class="col-md-3 col-6 mb-3"><a href=""><img src="https://via.placeholder.com/500x500" class="img-fluid" alt=""></a></div>
<div class="col-md-3 col-6 mb-3"><a href=""><img src="https://via.placeholder.com/500x500" class="img-fluid" alt=""></a></div>
</div>
</div>
</main>
{% endblock %}
from django.shortcuts import render
def home(request):
template_name = 'home.html'
data = {}
return render(request, template_name, data)
# catalog/context_processors.py
from catalog.models import Category
def categories(request):
items = Category.objects.filter(status=True)
return {
'items_categories': items
}
## main/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
...
'catalog.context_processors.categories',
],
},
},
]
...
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Productos
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
{% for category in items_categories %}
<li><a class="dropdown-item" href="{% url 'catalog:category' category.pk %}">{{category.name}}</a></li>
{% endfor %}
</ul>
</li>
...
from django.forms import ModelForm
from contacts.models import Contact
class ContactForm(ModelForm):
class Meta:
model = Contact
fields = '__all__'
def add_contact(request):
template_name = 'add_contact.html'
data = {}
if request.method == 'POST':
data['form'] = ContactForm(request.POST)
if data['form'].is_valid():
data['form'].save()
...
else:
data['form'] = ContactForm()
return render(request, template_name, data)
...
<form action="" method="post" class="form">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
{% endbuttons %}
</form>
...
str_msg = 'Registro exitoso!'
messages.add_message(
request,
messages.SUCCESS,
str_msg
)
{% if messages %}
{% for message in messages %}
<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
MESSAGE_TAGS = {
messages.DEBUG: 'alert-info',
messages.INFO: 'alert-info',
messages.SUCCESS: 'alert-success',
messages.WARNING: 'alert-warning',
messages.ERROR: 'alert-danger',
}
https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04-es
ssh-keygen -t rsa
git clone -b <branch> git@github.com:<user>/<repository>.git
cp docker-compose.yml docker-compose-prod.yml
## docker-compose-prod.yml
version: '3'
services:
web:
build: .
image: mcantillana/pycon:latest
command: gunicorn -b 0:8000 main.wsgi
env_file:
- .env
environment:
- VIRTUAL_HOST=demo.pycon.e2l.dev
- VIRTUAL_PORT=8000
- LETSENCRYPT_HOST=demo.pycon.e2l.dev
- LETSENCRYPT_EMAIL=mcantillana@linets.cl
expose:
- "8000"
ports:
- "8000:8000"
volumes:
- .:/code
networks:
- net-pycon
networks:
net-pycon:
external:
name: nginx-proxy
git clone https://github.com/mcantillana/nginx-proxy
docker-compose -f letsencrypt.yml -f docker-compose.yml up -d
docker-compose -f docker-compose-prod.yml up -d