sandwitches 1.0.1__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sandwitches/api.py ADDED
@@ -0,0 +1,65 @@
1
+ from ninja import NinjaAPI
2
+ from .models import Recipe
3
+
4
+ from ninja import ModelSchema
5
+ from ninja import Schema
6
+ from django.contrib.auth.models import User
7
+ from django.shortcuts import get_object_or_404
8
+ from datetime import date
9
+ import random
10
+
11
+
12
+ api = NinjaAPI()
13
+
14
+
15
+ class RecipeSchema(ModelSchema):
16
+ class Meta:
17
+ model = Recipe
18
+ fields = "__all__"
19
+
20
+
21
+ class UserSchema(ModelSchema):
22
+ class Meta:
23
+ model = User
24
+ exclude = ["password", "last_login", "user_permissions"]
25
+
26
+
27
+ class Error(Schema):
28
+ message: str
29
+
30
+
31
+ @api.get("v1/me", response={200: UserSchema, 403: Error})
32
+ def me(request):
33
+ if not request.user.is_authenticated:
34
+ return 403, {"message": "Please sign in first"}
35
+ return request.user
36
+
37
+
38
+ # TODO: enable recipe creation via API
39
+ # @api.post("v1/recipe", auth=django_auth, response=RecipeSchema)
40
+ # def create_recipe(request, payload: RecipeSchema):
41
+ # recipe = Recipe.objects.create(**payload.dict())
42
+ # return recipe
43
+
44
+
45
+ @api.get("v1/recipes", response=list[RecipeSchema])
46
+ def get_recipes(request):
47
+ recipes = Recipe.objects.all()
48
+ return recipes
49
+
50
+
51
+ @api.get("v1/recipes/{recipe_id}", response=RecipeSchema)
52
+ def get_recipe(request, recipe_id: int):
53
+ recipe = get_object_or_404(Recipe, id=recipe_id)
54
+ return recipe
55
+
56
+
57
+ @api.get("v1/recipe-of-the-day", response=RecipeSchema)
58
+ def get_recipe_of_the_day(request):
59
+ recipes = list(Recipe.objects.all())
60
+ if not recipes:
61
+ return None
62
+ today = date.today()
63
+ random.seed(today.toordinal())
64
+ recipe = random.choice(recipes)
65
+ return recipe
sandwitches/forms.py CHANGED
@@ -1,46 +1,65 @@
1
1
  from django import forms
2
2
  from django.contrib.auth import get_user_model
3
+ from django.contrib.auth.forms import UserCreationForm
3
4
  from .models import Recipe
4
5
 
5
6
  User = get_user_model()
6
7
 
7
8
 
8
- class AdminSetupForm(forms.Form):
9
- username = forms.CharField(max_length=150, label="Username")
10
- email = forms.EmailField(required=False, label="Email (optional)")
11
- password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
12
- password2 = forms.CharField(label="Confirm password", widget=forms.PasswordInput)
13
- first_name = forms.CharField(max_length=30, required=False, label="First name")
14
- last_name = forms.CharField(max_length=150, required=False, label="Last name")
9
+ class BaseUserFormMixin:
10
+ """Mixin to handle common password validation and user field processing."""
15
11
 
16
- def clean_username(self):
17
- username = self.cleaned_data.get("username")
18
- if User.objects.filter(username=username).exists():
19
- raise forms.ValidationError("A user with that username already exists.")
20
- return username
21
-
22
- def clean(self):
23
- cleaned = super().clean()
24
- p1 = cleaned.get("password1")
25
- p2 = cleaned.get("password2")
12
+ def clean_passwords(self, cleaned_data):
13
+ p1 = cleaned_data.get("password1")
14
+ p2 = cleaned_data.get("password2")
26
15
  if p1 and p2 and p1 != p2:
27
16
  raise forms.ValidationError("Passwords do not match.")
28
- return cleaned
17
+ return cleaned_data
18
+
19
+ def _set_user_attributes(self, user, data):
20
+ """Helper to apply optional name fields."""
21
+ user.first_name = data.get("first_name", "")
22
+ user.last_name = data.get("last_name", "")
23
+ user.save()
24
+ return user
25
+
26
+
27
+ class AdminSetupForm(forms.ModelForm, BaseUserFormMixin):
28
+ password1 = forms.CharField(widget=forms.PasswordInput, label="Password")
29
+ password2 = forms.CharField(widget=forms.PasswordInput, label="Confirm Password")
30
+
31
+ class Meta:
32
+ model = User
33
+ fields = ("username", "first_name", "last_name", "email")
34
+
35
+ def clean(self):
36
+ cleaned_data = super().clean()
37
+ return self.clean_passwords(cleaned_data)
29
38
 
30
- def save(self):
39
+ def save(self, commit=True):
31
40
  data = self.cleaned_data
32
41
  user = User.objects.create_superuser(
33
- username=data["username"],
34
- email=data.get("email") or "",
35
- password=data["password1"],
42
+ username=data["username"], email=data["email"], password=data["password1"]
36
43
  )
37
- # set optional names
38
- if data.get("first_name"):
39
- user.first_name = data["first_name"]
40
- if data.get("last_name"):
41
- user.last_name = data["last_name"]
42
- user.is_staff = True
43
- user.save()
44
+ return self._set_user_attributes(user, data)
45
+
46
+
47
+ class UserSignupForm(UserCreationForm, BaseUserFormMixin):
48
+ """Refactored Regular User Form inheriting from Django's UserCreationForm"""
49
+
50
+ class Meta(UserCreationForm.Meta):
51
+ model = User
52
+ fields = ("username", "first_name", "last_name", "email")
53
+
54
+ def clean(self):
55
+ return super().clean()
56
+
57
+ def save(self, commit=True):
58
+ user = super().save(commit=False)
59
+ user.is_superuser = False
60
+ user.is_staff = False
61
+ if commit:
62
+ user.save()
44
63
  return user
45
64
 
46
65
 
@@ -58,3 +77,13 @@ class RecipeForm(forms.ModelForm):
58
77
  widgets = {
59
78
  "tags": forms.TextInput(attrs={"placeholder": "tag1,tag2"}),
60
79
  }
80
+
81
+
82
+ class RatingForm(forms.Form):
83
+ """Simple form for rating recipes (1-5)."""
84
+
85
+ score = forms.ChoiceField(
86
+ choices=[(str(i), str(i)) for i in range(1, 6)],
87
+ widget=forms.RadioSelect,
88
+ label="Your rating",
89
+ )
@@ -0,0 +1,57 @@
1
+ # Generated by Django 6.0 on 2025-12-21 21:17
2
+
3
+ import django.db.models.deletion
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ dependencies = [
10
+ ("sandwitches", "0002_historicalrecipe"),
11
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name="Rating",
17
+ fields=[
18
+ (
19
+ "id",
20
+ models.BigAutoField(
21
+ auto_created=True,
22
+ primary_key=True,
23
+ serialize=False,
24
+ verbose_name="ID",
25
+ ),
26
+ ),
27
+ (
28
+ "score",
29
+ models.PositiveSmallIntegerField(
30
+ choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]
31
+ ),
32
+ ),
33
+ ("created_at", models.DateTimeField(auto_now_add=True)),
34
+ ("updated_at", models.DateTimeField(auto_now=True)),
35
+ (
36
+ "recipe",
37
+ models.ForeignKey(
38
+ on_delete=django.db.models.deletion.CASCADE,
39
+ related_name="ratings",
40
+ to="sandwitches.recipe",
41
+ ),
42
+ ),
43
+ (
44
+ "user",
45
+ models.ForeignKey(
46
+ on_delete=django.db.models.deletion.CASCADE,
47
+ related_name="ratings",
48
+ to=settings.AUTH_USER_MODEL,
49
+ ),
50
+ ),
51
+ ],
52
+ options={
53
+ "ordering": ("-updated_at",),
54
+ "unique_together": {("recipe", "user")},
55
+ },
56
+ ),
57
+ ]
sandwitches/models.py CHANGED
@@ -3,9 +3,13 @@ from django.urls import reverse
3
3
  from django.utils.text import slugify
4
4
  from .storage import HashedFilenameStorage
5
5
  from simple_history.models import HistoricalRecords
6
+ from django.contrib.auth import get_user_model
7
+ from django.db.models import Avg
6
8
 
7
9
  hashed_storage = HashedFilenameStorage()
8
10
 
11
+ User = get_user_model()
12
+
9
13
 
10
14
  class Tag(models.Model):
11
15
  name = models.CharField(max_length=50, unique=True)
@@ -87,8 +91,31 @@ class Recipe(models.Model):
87
91
  self.tags.set(tags)
88
92
  return self.tags.all()
89
93
 
94
+ # add helper methods for ratings
95
+ def average_rating(self):
96
+ agg = self.ratings.aggregate(avg=Avg("score"))
97
+ return agg["avg"] or 0
98
+
99
+ def rating_count(self):
100
+ return self.ratings.count()
101
+
90
102
  def get_absolute_url(self):
91
103
  return reverse("recipe_detail", kwargs={"pk": self.pk, "slug": self.slug})
92
104
 
93
105
  def __str__(self):
94
106
  return self.title
107
+
108
+
109
+ class Rating(models.Model):
110
+ recipe = models.ForeignKey(Recipe, related_name="ratings", on_delete=models.CASCADE)
111
+ user = models.ForeignKey(User, related_name="ratings", on_delete=models.CASCADE)
112
+ score = models.PositiveSmallIntegerField(choices=[(i, i) for i in range(1, 6)])
113
+ created_at = models.DateTimeField(auto_now_add=True)
114
+ updated_at = models.DateTimeField(auto_now=True)
115
+
116
+ class Meta:
117
+ unique_together = ("recipe", "user")
118
+ ordering = ("-updated_at",)
119
+
120
+ def __str__(self):
121
+ return f"{self.recipe} — {self.score} by {self.user}"
sandwitches/settings.py CHANGED
@@ -19,11 +19,16 @@ DEBUG = bool(os.environ.get("DEBUG", default=0))
19
19
  ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "127.0.0.1").split(",")
20
20
  CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(",")
21
21
 
22
+ # RECAPTCHA_PROXY = {'http': 'http://127.0.0.1:8000', 'https': 'https://127.0.0.1:8000'}
23
+ # RECAPTCHA_PUBLIC_KEY = os.environ.get("RECAPTCHA_PUBLIC_KEY")
24
+ # RECAPTCHA_PRIVATE_KEY = os.environ.get("RECAPTCHA_PRIVATE_KEY")
25
+
22
26
  # Build paths inside the project like this: BASE_DIR / 'subdir'.
23
27
  BASE_DIR = Path(__file__).resolve().parent.parent
24
28
 
25
- # Application definition
29
+ TASKS = {"default": {"BACKEND": "django.tasks.backends.immediate.ImmediateBackend"}}
26
30
 
31
+ # Application definition
27
32
  INSTALLED_APPS = [
28
33
  "django.contrib.admin",
29
34
  "django.contrib.auth",
@@ -33,6 +38,7 @@ INSTALLED_APPS = [
33
38
  "django.contrib.staticfiles",
34
39
  "sandwitches",
35
40
  "debug_toolbar",
41
+ # "django_recaptcha",
36
42
  "simple_history",
37
43
  ]
38
44
 
sandwitches/tasks.py ADDED
@@ -0,0 +1,16 @@
1
+ import logging
2
+ from django.core.mail import send_mail
3
+ from django.tasks import task
4
+
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ @task(takes_context=True, priority=2, queue_name="emails")
10
+ def email_users(context, emails, subject, message):
11
+ logger.debug(
12
+ f"Attempt {context.attempt} to send user email. Task result id: {context.task_result.id}."
13
+ )
14
+ return send_mail(
15
+ subject=subject, message=message, from_email=None, recipient_list=emails
16
+ )
@@ -168,11 +168,13 @@
168
168
  </li>
169
169
  </ul>
170
170
  <ul>
171
+ <li><a href="api/docs">Docs</a></li>
171
172
  {% if user.is_authenticated %}
172
173
  <li><a href="{% url 'admin:index' %}">Admin</a></li>
173
174
  <li><a href="{% url 'admin:logout' %}">Logout</a></li>
174
175
  {% else %}
175
176
  <li><a href="{% url 'admin:login' %}">Login</a></li>
177
+ <li><a href="{% url 'signup' %}">Sign up</a></li>
176
178
  {% endif %}
177
179
  </ul>
178
180
  </nav>
@@ -34,6 +34,66 @@
34
34
  {% endfor %}
35
35
  </div>
36
36
  </div>
37
+
38
+ <div style="margin-top:1rem;">
39
+ <h4>Rating</h4>
40
+ {% if rating_count %}
41
+ <p>Average: {{ avg_rating|floatformat:1 }} ({{ rating_count }} vote{% if rating_count %}s{% endif %})</p>
42
+ {% else %}
43
+ <p>No ratings yet.</p>
44
+ {% endif %}
45
+
46
+ {% if user.is_authenticated %}
47
+ {% if user_rating %}
48
+ <p>Your rating: {{ user_rating.score }}</p>
49
+ {% else %}
50
+ <form method="post" action="{% url 'recipe_rate' pk=recipe.pk %}">
51
+ {% csrf_token %}
52
+
53
+ <fieldset>
54
+ <legend>What would you rate this sandwich?</legend>
55
+
56
+ {% if rating_form.score.errors %}
57
+ <div class="card-panel" role="alert">
58
+ <ul>
59
+ {% for err in rating_form.score.errors %}
60
+ <li>{{ err }}</li>
61
+ {% endfor %}
62
+ </ul>
63
+ </div>
64
+ {% endif %}
65
+
66
+ <div class="row">
67
+ {% for i in "12345"|make_list %}
68
+ <div class="col-sm-2">
69
+ <label class="form-check">
70
+ <input
71
+ type="radio"
72
+ name="{{ rating_form.score.name }}"
73
+ id="rating-{{ i }}"
74
+ value="{{ i }}"
75
+ {% if user_rating and user_rating.score|stringformat:"s" == i %}
76
+ checked
77
+ {% elif rating_form.score.value == i %}
78
+ checked
79
+ {% endif %}
80
+ />
81
+ <span>{{ i }}</span>
82
+ </label>
83
+ </div>
84
+ {% endfor %}
85
+ </div>
86
+ </fieldset>
87
+ <p style="margin-top:0.5rem;">
88
+ <button type="submit">{% if user_rating %}Update{% else %}Rate{% endif %}</button>
89
+ </p>
90
+ </form>
91
+ {% endif %}
92
+ {% else %}
93
+ <p><a href="{% url 'admin:login' %}">Log in</a> to rate this recipe.</p>
94
+ {% endif %}
95
+ </div>
96
+
37
97
  {% if user.is_authenticated and user.is_staff %}
38
98
  <footer>
39
99
  <a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/">Edit</a>
@@ -0,0 +1,50 @@
1
+ {% extends "base_pico.html" %}
2
+ {% load static %}
3
+ {% block title %}Sign Up{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="container" style="max-width:720px; margin:2rem auto;">
7
+ <article class="card">
8
+ <div class="card-body">
9
+ <h2>Sign up</h2>
10
+
11
+ <form method="post" novalidate>
12
+ {% csrf_token %}
13
+ {% if form.non_field_errors %}
14
+ <div class="card-panel" role="alert">
15
+ <ul>
16
+ {% for err in form.non_field_errors %}
17
+ <li>{{ err }}</li>
18
+ {% endfor %}
19
+ </ul>
20
+ </div>
21
+ {% endif %}
22
+
23
+ <label for="{{ form.username.id_for_label }}">Username</label>
24
+ {{ form.username }}
25
+
26
+ <label for="{{ form.email.id_for_label }}">Email (optional)</label>
27
+ {{ form.email }}
28
+
29
+ <label for="{{ form.first_name.id_for_label }}">First name</label>
30
+ {{ form.first_name }}
31
+
32
+ <label for="{{ form.last_name.id_for_label }}">Last name</label>
33
+ {{ form.last_name }}
34
+
35
+ <label for="{{ form.password1.id_for_label }}">Password</label>
36
+ {{ form.password1 }}
37
+
38
+ <label for="{{ form.password2.id_for_label }}">Confirm password</label>
39
+ {{ form.password2 }}
40
+
41
+ <p style="margin-top:1rem;">
42
+ <button type="submit">Sign Up</button>
43
+ <a class="contrast" href="{% url 'index' %}">Cancel</a>
44
+ </p>
45
+ </form>
46
+ </div>
47
+ </article>
48
+ </div>
49
+
50
+ {% endblock %}
sandwitches/urls.py CHANGED
@@ -18,6 +18,8 @@ Including another URLconf
18
18
  from django.contrib import admin
19
19
  from django.urls import path
20
20
  from . import views
21
+ from .api import api
22
+
21
23
 
22
24
  from django.conf import settings
23
25
  from django.conf.urls.static import static
@@ -31,6 +33,9 @@ urlpatterns = [
31
33
  path("admin/", admin.site.urls),
32
34
  path("recipes/<slug:slug>/", views.recipe_detail, name="recipe_detail"),
33
35
  path("setup/", views.setup, name="setup"),
36
+ path("api/", api.urls),
37
+ path("signup/", views.signup, name="signup"),
38
+ path("recipes/<int:pk>/rate/", views.recipe_rate, name="recipe_rate"),
34
39
  ]
35
40
 
36
41
  if "test" not in sys.argv or "PYTEST_VERSION" in os.environ:
sandwitches/views.py CHANGED
@@ -3,9 +3,10 @@ from django.urls import reverse
3
3
  from django.contrib import messages
4
4
  from django.contrib.auth import login
5
5
  from django.contrib.auth import get_user_model
6
+ from django.contrib.auth.decorators import login_required
6
7
 
7
- from .models import Recipe
8
- from .forms import RecipeForm, AdminSetupForm
8
+ from .models import Recipe, Rating
9
+ from .forms import RecipeForm, AdminSetupForm, UserSignupForm, RatingForm
9
10
 
10
11
  User = get_user_model()
11
12
 
@@ -24,7 +25,50 @@ def recipe_edit(request, pk):
24
25
 
25
26
  def recipe_detail(request, slug):
26
27
  recipe = get_object_or_404(Recipe, slug=slug)
27
- return render(request, "detail.html", {"recipe": recipe})
28
+ avg = recipe.average_rating()
29
+ count = recipe.rating_count()
30
+ user_rating = None
31
+ rating_form = None
32
+ if request.user.is_authenticated:
33
+ try:
34
+ user_rating = Rating.objects.get(recipe=recipe, user=request.user)
35
+ except Rating.DoesNotExist:
36
+ user_rating = None
37
+ # show form prefilled when possible
38
+ initial = {"score": str(user_rating.score)} if user_rating else None
39
+ rating_form = RatingForm(initial=initial)
40
+ return render(
41
+ request,
42
+ "detail.html",
43
+ {
44
+ "recipe": recipe,
45
+ "avg_rating": avg,
46
+ "rating_count": count,
47
+ "user_rating": user_rating,
48
+ "rating_form": rating_form,
49
+ },
50
+ )
51
+
52
+
53
+ @login_required
54
+ def recipe_rate(request, pk):
55
+ """
56
+ Create or update a rating for the given recipe by the logged-in user.
57
+ """
58
+ recipe = get_object_or_404(Recipe, pk=pk)
59
+ if request.method != "POST":
60
+ return redirect("recipe_detail", slug=recipe.slug)
61
+
62
+ form = RatingForm(request.POST)
63
+ if form.is_valid():
64
+ score = int(form.cleaned_data["score"])
65
+ Rating.objects.update_or_create(
66
+ recipe=recipe, user=request.user, defaults={"score": score}
67
+ )
68
+ messages.success(request, "Your rating has been saved.")
69
+ else:
70
+ messages.error(request, "Could not save rating.")
71
+ return redirect("recipe_detail", slug=recipe.slug)
28
72
 
29
73
 
30
74
  def index(request):
@@ -56,3 +100,22 @@ def setup(request):
56
100
  form = AdminSetupForm()
57
101
 
58
102
  return render(request, "setup.html", {"form": form})
103
+
104
+
105
+ def signup(request):
106
+ """
107
+ User signup page: create new regular user accounts.
108
+ """
109
+ if request.method == "POST":
110
+ form = UserSignupForm(request.POST)
111
+ if form.is_valid():
112
+ user = form.save()
113
+ # log in the newly created user
114
+ user.backend = "django.contrib.auth.backends.ModelBackend"
115
+ login(request, user)
116
+ messages.success(request, "Account created and signed in.")
117
+ return redirect("index")
118
+ else:
119
+ form = UserSignupForm()
120
+
121
+ return render(request, "signup.html", {"form": form})
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sandwitches
3
- Version: 1.0.1
3
+ Version: 1.1.0
4
4
  Summary: Add your description here
5
5
  Author: Martyn van Dijke
6
6
  Author-email: Martyn van Dijke <martijnvdijke600@gmail.com>
7
7
  Requires-Dist: django-debug-toolbar>=6.1.0
8
8
  Requires-Dist: django-filter>=25.2
9
+ Requires-Dist: django-ninja>=1.5.1
9
10
  Requires-Dist: django-simple-history>=3.10.1
10
- Requires-Dist: django>=5.2.7
11
- Requires-Dist: djangorestframework>=3.16.1
11
+ Requires-Dist: django>=6.0.0
12
12
  Requires-Dist: gunicorn>=23.0.0
13
13
  Requires-Dist: markdown>=3.10
14
14
  Requires-Dist: pillow>=12.0.0
@@ -1,24 +1,28 @@
1
1
  sandwitches/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sandwitches/admin.py,sha256=8QyK9xOUvGGPz30-7p2phyc-Ks9VKD_K1kCAagisPwA,120
3
+ sandwitches/api.py,sha256=HaTOgqGZK4sPUoGx1IFJoUQntcB1EX91pnyM7-TREjg,1565
3
4
  sandwitches/asgi.py,sha256=cygnXdXSSVspM7ZXuj47Ef6oz7HSTw4D7BPzgE2PU5w,399
4
- sandwitches/forms.py,sha256=EHTENN49BZRm0Ba2sU4Or9NVrO6GhkOaW-rsKcGjClA,1984
5
+ sandwitches/forms.py,sha256=NeGUi3xPzQpgw2cVWpo2ZKpYUXsCuJ3SzgGUkvxdV2A,2610
5
6
  sandwitches/migrations/0001_initial.py,sha256=01IfkFbUyYMpTHV5GaBxJEKjzRIdUdPR5sY3AUTODww,2546
6
7
  sandwitches/migrations/0002_historicalrecipe.py,sha256=yU2KYssfjYhPXRYN6C8IMRFr-4QUGJozz-O167XuabM,2499
8
+ sandwitches/migrations/0003_rating.py,sha256=iKk9M9lcBS5LwsJkMYsmYfHKqKs2QRTILgINbnilASM,1857
7
9
  sandwitches/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- sandwitches/models.py,sha256=HiPeh9OF1WoqWTjK5uUNuS5eLbtZBHfuXyg8h9Stb2M,3066
9
- sandwitches/settings.py,sha256=vPk91YEhXlda2ADDla0EcReV_e0gpRsAJylkZI_GS34,3498
10
+ sandwitches/models.py,sha256=7dJ5vHZ1bQWG2ebAJKEwaPHMukgdPth7i6quswRldmw,3974
11
+ sandwitches/settings.py,sha256=ApYwzWeJGX5Af4zJ6wWKq0-u8Lro8RVfO7xS7PRbpmw,3829
10
12
  sandwitches/storage.py,sha256=XrzMw8mcUowEoV5hYjP-ZI27C3vfk010UkukA5SrGDk,917
13
+ sandwitches/tasks.py,sha256=rsCpfCwcIoSthpN0IZ_dt2jPsMSUe8otBmE2UbFeKg0,468
11
14
  sandwitches/templates/base.html,sha256=gUOxxwD7G8VeBranSYp40PCWsJ-ywf8e7Qyv8VoqRP4,494
12
- sandwitches/templates/base_pico.html,sha256=ZjE04fUCQU3iLiJts38qQ-Lkg4tbnwnJ0YfzUB8PVlc,7378
13
- sandwitches/templates/detail.html,sha256=1hOxlMbj7N6tK_u4t06iETe9lmKF0XSXv2fFp8Yr1hA,1242
15
+ sandwitches/templates/base_pico.html,sha256=ntvh9wrfVqkqI58LregrPuVr9czsYB75n7HazbbkFhM,7477
16
+ sandwitches/templates/detail.html,sha256=1GuCwmIV-k9ROhR9bddv19rjqgC5CbLs3AzounmnAEE,3723
14
17
  sandwitches/templates/form.html,sha256=XgrQfb_ZEJfnAou7z3gxcj7wqZ4fwFANplvYdzXg80A,373
15
18
  sandwitches/templates/index.html,sha256=fnsU5N5dPaa3BXOoIUvgD-9a3EPz0Fs25M0p_eQnf5w,2307
16
19
  sandwitches/templates/setup.html,sha256=Y45l96Tl6ln9Mb5gCC4JnT1bf-zuQOpYYL1JL3k-1T0,1677
20
+ sandwitches/templates/signup.html,sha256=UR5Doueq58hSUQgqDrhH5y5Ivdxp_8cfZ0TPaTW_IrI,1439
17
21
  sandwitches/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
22
  sandwitches/templatetags/markdown_extras.py,sha256=0ibmRzxE3r85x4k7kK71R-9UT0CgeegYF7MHzj3juTI,344
19
- sandwitches/urls.py,sha256=VsN-bLzAPgYAHm_sCJ36F5y_-eMHuPzGT9Trw7dGSSU,1398
20
- sandwitches/views.py,sha256=_-s7vtJ-n5fnCTnzytFvKK-CMY6Wz5xCb5RKnbI1T4Y,1940
23
+ sandwitches/urls.py,sha256=dBZ4o5lQqAapIVEIZ1OkNTWmU16tUoG3LygusDREaHY,1573
24
+ sandwitches/views.py,sha256=W8HvLFkdmS-nDj3sDY8IZWXv4V7CU5riQy75HSkbEqM,4001
21
25
  sandwitches/wsgi.py,sha256=Eyncpnahq_4s3Lr9ruB-R3Lu9j9zBXqgPbUj7qhIbwU,399
22
- sandwitches-1.0.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
23
- sandwitches-1.0.1.dist-info/METADATA,sha256=wEb0ellfkkcXCDFZ2V0Uk5MgGDBKysK_Rx66EOQBZGc,621
24
- sandwitches-1.0.1.dist-info/RECORD,,
26
+ sandwitches-1.1.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
27
+ sandwitches-1.1.0.dist-info/METADATA,sha256=f4X5Ovu99_ADGnBoWqgWGP5xZKmU6Y5WWFzWQqtrRvA,613
28
+ sandwitches-1.1.0.dist-info/RECORD,,