[foods] Add Food scrobbling
This commit is contained in:
@ -132,6 +132,6 @@ class Beer(ScrobblableMixin):
|
|||||||
|
|
||||||
def scrobbles(self, user_id):
|
def scrobbles(self, user_id):
|
||||||
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
||||||
return Scrobble.objects.filter(
|
return Scrobble.objects.filter(user_id=user_id, beer=self).order_by(
|
||||||
user_id=user_id, life_event=self
|
"-timestamp"
|
||||||
).order_by("-timestamp")
|
)
|
||||||
|
|||||||
28
vrobbler/apps/foods/admin.py
Normal file
28
vrobbler/apps/foods/admin.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from foods.models import Food, FoodCategory
|
||||||
|
from django.contrib import admin
|
||||||
|
from scrobbles.admin import ScrobbleInline
|
||||||
|
|
||||||
|
|
||||||
|
class FoodInline(admin.TabularInline):
|
||||||
|
model = Food
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(FoodCategory)
|
||||||
|
class FoodCategoryAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
search_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Food)
|
||||||
|
class FoodAdmin(admin.ModelAdmin):
|
||||||
|
date_hierarchy = "created"
|
||||||
|
list_display = (
|
||||||
|
"uuid",
|
||||||
|
"title",
|
||||||
|
)
|
||||||
|
ordering = ("-created",)
|
||||||
|
search_fields = ("title",)
|
||||||
|
inlines = [
|
||||||
|
ScrobbleInline,
|
||||||
|
]
|
||||||
143
vrobbler/apps/foods/allrecipe.py
Normal file
143
vrobbler/apps/foods/allrecipe.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import urllib
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ALLRECIPE_URL = "https://allrecipe.com/{id}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_first(key: str, result: dict) -> str:
|
||||||
|
obj = ""
|
||||||
|
if obj_list := result.get(key):
|
||||||
|
obj = obj_list[0]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def get_title_from_soup(soup) -> str:
|
||||||
|
title = ""
|
||||||
|
try:
|
||||||
|
title = soup.find("h1").get_text()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return title
|
||||||
|
|
||||||
|
|
||||||
|
def get_description_from_soup(soup) -> str:
|
||||||
|
desc = ""
|
||||||
|
try:
|
||||||
|
desc = (
|
||||||
|
soup.find(class_="beer-descrption-read-less")
|
||||||
|
.get_text()
|
||||||
|
.replace("Show Less", "")
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return desc
|
||||||
|
|
||||||
|
|
||||||
|
def get_styles_from_soup(soup) -> list[str]:
|
||||||
|
styles = []
|
||||||
|
try:
|
||||||
|
styles = soup.find("p", class_="style").get_text().split(" - ")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return styles
|
||||||
|
|
||||||
|
|
||||||
|
def get_abv_from_soup(soup) -> Optional[float]:
|
||||||
|
abv = None
|
||||||
|
try:
|
||||||
|
abv = soup.find(class_="abv").get_text()
|
||||||
|
if abv:
|
||||||
|
abv = float(abv.strip("\n").strip("% ABV").strip())
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
return abv
|
||||||
|
|
||||||
|
|
||||||
|
def get_ibu_from_soup(soup) -> Optional[int]:
|
||||||
|
ibu = None
|
||||||
|
try:
|
||||||
|
ibu = soup.find(class_="ibu").get_text()
|
||||||
|
if ibu:
|
||||||
|
ibu = int(ibu.strip("\n").strip(" IBU").strip())
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
ibu = None
|
||||||
|
return ibu
|
||||||
|
|
||||||
|
|
||||||
|
def get_rating_from_soup(soup) -> str:
|
||||||
|
rating = ""
|
||||||
|
try:
|
||||||
|
rating = float(
|
||||||
|
soup.find(class_="num").get_text().strip("(").strip(")")
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
rating = None
|
||||||
|
except ValueError:
|
||||||
|
rating = None
|
||||||
|
return rating
|
||||||
|
|
||||||
|
|
||||||
|
def get_producer_id_from_soup(soup) -> str:
|
||||||
|
id = ""
|
||||||
|
try:
|
||||||
|
id = soup.find(class_="brewery").find("a")["href"].strip("/")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
return id
|
||||||
|
|
||||||
|
|
||||||
|
def get_producer_name_from_soup(soup) -> str:
|
||||||
|
name = ""
|
||||||
|
try:
|
||||||
|
name = soup.find(class_="brewery").find("a").get_text()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def get_food_from_allrecipe_id(allrecipe_id: str) -> dict:
|
||||||
|
url = ALLRECIPE_URL.format(id=allrecipe_id)
|
||||||
|
headers = {"User-Agent": "Vrobbler 0.11.12"}
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
food_dict = {"allrecipe_id": allrecipe_id}
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.warn(
|
||||||
|
"Bad response from allrecipe", extra={"response": response}
|
||||||
|
)
|
||||||
|
return food_dict
|
||||||
|
|
||||||
|
import pdb
|
||||||
|
|
||||||
|
pdb.set_trace()
|
||||||
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
|
food_dict["title"] = get_title_from_soup(soup)
|
||||||
|
food_dict["description"] = get_description_from_soup(soup)
|
||||||
|
food_dict["allrecipe_rating"] = get_rating_from_soup(soup)
|
||||||
|
|
||||||
|
return food_dict
|
||||||
5
vrobbler/apps/foods/apps.py
Normal file
5
vrobbler/apps/foods/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class FoodsConfig(AppConfig):
|
||||||
|
name = "foods"
|
||||||
151
vrobbler/apps/foods/migrations/0001_initial.py
Normal file
151
vrobbler/apps/foods/migrations/0001_initial.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2024-11-20 19:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_extensions.db.fields
|
||||||
|
import taggit.managers
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("scrobbles", "0067_scrobble_food_alter_scrobble_media_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="FoodCategory",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
django_extensions.db.fields.CreationDateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
django_extensions.db.fields.ModificationDateTimeField(
|
||||||
|
auto_now=True, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
blank=True,
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
(
|
||||||
|
"allrecipe_image",
|
||||||
|
models.ImageField(
|
||||||
|
blank=True, null=True, upload_to="food/recipe/"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"allrecipe_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
("description", models.TextField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"get_latest_by": "modified",
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Food",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
django_extensions.db.fields.CreationDateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
django_extensions.db.fields.ModificationDateTimeField(
|
||||||
|
auto_now=True, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(
|
||||||
|
blank=True,
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"run_time_seconds",
|
||||||
|
models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"run_time_ticks",
|
||||||
|
models.PositiveBigIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
("description", models.TextField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"allrecipe_image",
|
||||||
|
models.ImageField(
|
||||||
|
blank=True, null=True, upload_to="food/recipe/"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"allrecipe_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
("allrecipe_rating", models.FloatField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"category",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
to="foods.foodcategory",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"genre",
|
||||||
|
taggit.managers.TaggableManager(
|
||||||
|
blank=True,
|
||||||
|
help_text="A comma-separated list of tags.",
|
||||||
|
through="scrobbles.ObjectWithGenres",
|
||||||
|
to="scrobbles.Genre",
|
||||||
|
verbose_name="Tags",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
vrobbler/apps/foods/migrations/__init__.py
Normal file
0
vrobbler/apps/foods/migrations/__init__.py
Normal file
113
vrobbler/apps/foods/models.py
Normal file
113
vrobbler/apps/foods/models.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from django_extensions.db.models import TimeStampedModel
|
||||||
|
from imagekit.models import ImageSpecField
|
||||||
|
from imagekit.processors import ResizeToFit
|
||||||
|
from scrobbles.dataclasses import FoodLogData
|
||||||
|
from scrobbles.mixins import ScrobblableConstants, ScrobblableMixin
|
||||||
|
|
||||||
|
BNULL = {"blank": True, "null": True}
|
||||||
|
|
||||||
|
|
||||||
|
class FoodCategory(TimeStampedModel):
|
||||||
|
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
allrecipe_image = models.ImageField(upload_to="food/recipe/", **BNULL)
|
||||||
|
allrecipe_image_small = ImageSpecField(
|
||||||
|
source="recipe_image",
|
||||||
|
processors=[ResizeToFit(100, 100)],
|
||||||
|
format="JPEG",
|
||||||
|
options={"quality": 60},
|
||||||
|
)
|
||||||
|
allrecipe_image_medium = ImageSpecField(
|
||||||
|
source="recipe_image",
|
||||||
|
processors=[ResizeToFit(300, 300)],
|
||||||
|
format="JPEG",
|
||||||
|
options={"quality": 75},
|
||||||
|
)
|
||||||
|
allrecipe_id = models.CharField(max_length=255, **BNULL)
|
||||||
|
description = models.TextField(**BNULL)
|
||||||
|
|
||||||
|
def find_or_create(cls, title: str) -> "FoodCategory":
|
||||||
|
return cls.objects.filter(title=title).first()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
class Food(ScrobblableMixin):
|
||||||
|
description = models.TextField(**BNULL)
|
||||||
|
allrecipe_image = models.ImageField(upload_to="food/recipe/", **BNULL)
|
||||||
|
allrecipe_image_small = ImageSpecField(
|
||||||
|
source="recipe_image",
|
||||||
|
processors=[ResizeToFit(100, 100)],
|
||||||
|
format="JPEG",
|
||||||
|
options={"quality": 60},
|
||||||
|
)
|
||||||
|
allrecipe_image_medium = ImageSpecField(
|
||||||
|
source="recipe_image",
|
||||||
|
processors=[ResizeToFit(300, 300)],
|
||||||
|
format="JPEG",
|
||||||
|
options={"quality": 75},
|
||||||
|
)
|
||||||
|
allrecipe_id = models.CharField(max_length=255, **BNULL)
|
||||||
|
allrecipe_rating = models.FloatField(**BNULL)
|
||||||
|
category = models.ForeignKey(
|
||||||
|
FoodCategory, on_delete=models.DO_NOTHING, **BNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_absolute_url(self) -> str:
|
||||||
|
return reverse("foods:food_detail", kwargs={"slug": self.uuid})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subtitle(self):
|
||||||
|
return self.category.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strings(self) -> ScrobblableConstants:
|
||||||
|
return ScrobblableConstants(verb="Eating", tags="food")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allrecipe_link(self) -> str:
|
||||||
|
link = ""
|
||||||
|
if self.producer and self.allrecipe_id:
|
||||||
|
if self.allrecipe_id:
|
||||||
|
link = f"https://www.allrecipe.com/{self.allrecipe_id}/"
|
||||||
|
return link
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primary_image_url(self) -> str:
|
||||||
|
url = ""
|
||||||
|
if self.allrecipe_image:
|
||||||
|
url = self.allrecipe_image.url
|
||||||
|
return url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logdata_cls(self):
|
||||||
|
return FoodLogData
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_or_create(cls, allrecipe_id: str) -> "Food":
|
||||||
|
food = cls.objects.filter(allrecipe_id=allrecipe_id).first()
|
||||||
|
|
||||||
|
if not food:
|
||||||
|
food_dict = get_food_from_allrecipe_id(allrecipe_id)
|
||||||
|
# category_dict = {}
|
||||||
|
|
||||||
|
# category, _created = FoodCategory.objects.get_or_create(
|
||||||
|
# **category_dict
|
||||||
|
# )
|
||||||
|
food = Food.objects.create(**food_dict)
|
||||||
|
# for category_id in category_ids:
|
||||||
|
# food.category.add(category_id)
|
||||||
|
|
||||||
|
return food
|
||||||
|
|
||||||
|
def scrobbles(self, user_id):
|
||||||
|
Scrobble = apps.get_model("scrobbles", "Scrobble")
|
||||||
|
return Scrobble.objects.filter(user_id=user_id, food=self).order_by(
|
||||||
|
"-timestamp"
|
||||||
|
)
|
||||||
14
vrobbler/apps/foods/urls.py
Normal file
14
vrobbler/apps/foods/urls.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from foods import views
|
||||||
|
|
||||||
|
app_name = "foods"
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("foods/", views.FoodListView.as_view(), name="food_list"),
|
||||||
|
path(
|
||||||
|
"foods/<slug:slug>/",
|
||||||
|
views.FoodDetailView.as_view(),
|
||||||
|
name="food_detail",
|
||||||
|
),
|
||||||
|
]
|
||||||
11
vrobbler/apps/foods/views.py
Normal file
11
vrobbler/apps/foods/views.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from foods.models import Food
|
||||||
|
|
||||||
|
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
|
||||||
|
|
||||||
|
|
||||||
|
class FoodListView(ScrobbleableListView):
|
||||||
|
model = Food
|
||||||
|
|
||||||
|
|
||||||
|
class FoodDetailView(ScrobbleableDetailView):
|
||||||
|
model = Food
|
||||||
@ -203,3 +203,11 @@ class BeerLogData(WithOthersLogData):
|
|||||||
details: Optional[str] = None
|
details: Optional[str] = None
|
||||||
rating: Optional[str] = None
|
rating: Optional[str] = None
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FoodLogData(JSONDataclass):
|
||||||
|
meal: Optional[str] = None
|
||||||
|
details: Optional[str] = None
|
||||||
|
rating: Optional[str] = None
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
# Generated by Django 4.2.16 on 2024-11-20 19:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("foods", "__first__"),
|
||||||
|
("scrobbles", "0066_scrobble_beer_alter_scrobble_media_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="food",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.DO_NOTHING,
|
||||||
|
to="foods.food",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="scrobble",
|
||||||
|
name="media_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("Video", "Video"),
|
||||||
|
("Track", "Track"),
|
||||||
|
("PodcastEpisode", "Podcast episode"),
|
||||||
|
("SportEvent", "Sport event"),
|
||||||
|
("Book", "Book"),
|
||||||
|
("VideoGame", "Video game"),
|
||||||
|
("BoardGame", "Board game"),
|
||||||
|
("GeoLocation", "GeoLocation"),
|
||||||
|
("Trail", "Trail"),
|
||||||
|
("Beer", "Beer"),
|
||||||
|
("Food", "Food"),
|
||||||
|
("Task", "Task"),
|
||||||
|
("WebPage", "Web Page"),
|
||||||
|
("LifeEvent", "Life event"),
|
||||||
|
("Mood", "Mood"),
|
||||||
|
("BrickSet", "Brick set"),
|
||||||
|
],
|
||||||
|
default="Video",
|
||||||
|
max_length=14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -19,6 +19,7 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django_extensions.db.models import TimeStampedModel
|
from django_extensions.db.models import TimeStampedModel
|
||||||
|
from foods.models import Food
|
||||||
from imagekit.models import ImageSpecField
|
from imagekit.models import ImageSpecField
|
||||||
from imagekit.processors import ResizeToFit
|
from imagekit.processors import ResizeToFit
|
||||||
from lifeevents.models import LifeEvent
|
from lifeevents.models import LifeEvent
|
||||||
@ -506,6 +507,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
GEO_LOCATION = "GeoLocation", "GeoLocation"
|
||||||
TRAIL = "Trail", "Trail"
|
TRAIL = "Trail", "Trail"
|
||||||
BEER = "Beer", "Beer"
|
BEER = "Beer", "Beer"
|
||||||
|
FOOD = "Food", "Food"
|
||||||
TASK = "Task", "Task"
|
TASK = "Task", "Task"
|
||||||
WEBPAGE = "WebPage", "Web Page"
|
WEBPAGE = "WebPage", "Web Page"
|
||||||
LIFE_EVENT = "LifeEvent", "Life event"
|
LIFE_EVENT = "LifeEvent", "Life event"
|
||||||
@ -532,6 +534,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
GeoLocation, on_delete=models.DO_NOTHING, **BNULL
|
GeoLocation, on_delete=models.DO_NOTHING, **BNULL
|
||||||
)
|
)
|
||||||
beer = models.ForeignKey(Beer, on_delete=models.DO_NOTHING, **BNULL)
|
beer = models.ForeignKey(Beer, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
|
food = models.ForeignKey(Food, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL)
|
trail = models.ForeignKey(Trail, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, **BNULL)
|
task = models.ForeignKey(Task, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL)
|
web_page = models.ForeignKey(WebPage, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
|
|||||||
@ -131,6 +131,7 @@ INSTALLED_APPS = [
|
|||||||
"tasks",
|
"tasks",
|
||||||
"trails",
|
"trails",
|
||||||
"beers",
|
"beers",
|
||||||
|
"foods",
|
||||||
"lifeevents",
|
"lifeevents",
|
||||||
"moods",
|
"moods",
|
||||||
"mathfilters",
|
"mathfilters",
|
||||||
|
|||||||
@ -38,6 +38,7 @@ from vrobbler.apps.sports.api.views import (
|
|||||||
from vrobbler.apps.tasks import urls as tasks_urls
|
from vrobbler.apps.tasks import urls as tasks_urls
|
||||||
from vrobbler.apps.trails import urls as trails_urls
|
from vrobbler.apps.trails import urls as trails_urls
|
||||||
from vrobbler.apps.beers import urls as beers_urls
|
from vrobbler.apps.beers import urls as beers_urls
|
||||||
|
from vrobbler.apps.foods import urls as foods_urls
|
||||||
from vrobbler.apps.videogames import urls as videogame_urls
|
from vrobbler.apps.videogames import urls as videogame_urls
|
||||||
from vrobbler.apps.videos import urls as video_urls
|
from vrobbler.apps.videos import urls as video_urls
|
||||||
from vrobbler.apps.videos.api.views import SeriesViewSet, VideoViewSet
|
from vrobbler.apps.videos.api.views import SeriesViewSet, VideoViewSet
|
||||||
@ -79,7 +80,7 @@ urlpatterns = [
|
|||||||
path("", include(sports_urls, namespace="sports")),
|
path("", include(sports_urls, namespace="sports")),
|
||||||
path("", include(locations_urls, namespace="locations")),
|
path("", include(locations_urls, namespace="locations")),
|
||||||
path("", include(trails_urls, namespace="trails")),
|
path("", include(trails_urls, namespace="trails")),
|
||||||
path("", include(beers_urls, namespace="beers")),
|
path("", include(foods_urls, namespace="foods")),
|
||||||
path("", include(tasks_urls, namespace="tasks")),
|
path("", include(tasks_urls, namespace="tasks")),
|
||||||
path("", include(webpages_urls, namespace="webpages")),
|
path("", include(webpages_urls, namespace="webpages")),
|
||||||
path("", include(podcast_urls, namespace="podcasts")),
|
path("", include(podcast_urls, namespace="podcasts")),
|
||||||
|
|||||||
Reference in New Issue
Block a user