"""Django models for the feeds app."""
from __future__ import annotations
import typing
from django.contrib.auth.models import User
from django.db import models
FEED_VERSION_CHOICES: tuple = (
("atom", "Atom (unknown or unrecognized version)"),
("atom01", "Atom 0.1"),
("atom02", "Atom 0.2"),
("atom03", "Atom 0.3"),
("atom10", "Atom 1.0"),
("cdf", "CDF"),
("rss", "RSS (unknown or unrecognized version)"),
("rss090", "RSS 0.90"),
("rss091n", "Netscape RSS 0.91"),
("rss091u", "Userland RSS 0.91"),
("rss092", "RSS 0.92"),
("rss093", "RSS 0.93"),
("rss094", "RSS 0.94 (no accurate specification is known to exist)"),
("rss10", "RSS 1.0"),
("rss20", "RSS 2.0"),
)
class Author(models.Model):
"""Details about the author of a feed.
Comes from the following elements:
- /atom03:feed/atom03:author
- /atom10:feed/atom10:author
- /rdf:RDF/rdf:channel/dc:author
- /rdf:RDF/rdf:channel/dc:creator
- /rss/channel/dc:author
- /rss/channel/dc:creator
- /rss/channel/itunes:author
- /rss/channel/managingEditor
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
name = models.TextField(blank=True, help_text="The name of the feed author.")
email = models.EmailField(blank=True, help_text="The email address of the feed author.")
# If this is a relative URI, it is resolved according to a set of rules:
# https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
href = models.URLField(
blank=True,
help_text="The URL of the feed author. This can be the author's home page, or a contact page with a webmail form.", # noqa: E501
)
class Meta:
"""Author meta."""
verbose_name: typing.ClassVar[str] = "Feed author"
verbose_name_plural: typing.ClassVar[str] = "Feed authors"
db_table_comment: typing.ClassVar[str] = "Details about the author of a feed."
def __str__(self: Author) -> str:
"""Author."""
return f"{self.name} {self.email} {self.href}"
class Contributor(models.Model):
"""Details about the contributor to a feed.
Comes from the following elements:
- /atom03:feed/atom03:contributor
- /atom10:feed/atom10:contributor
- /rss/channel/dc:contributor
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
name = models.TextField(blank=True, help_text="The name of this contributor.")
email = models.EmailField(blank=True, help_text="The email address of this contributor.")
# If this is a relative URI, it is resolved according to a set of rules:
# https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
href = models.URLField(
blank=True,
help_text="The URL of this contributor. This can be the contributor's home page, or a contact page with a webmail form.", # noqa: E501
)
class Meta:
"""Contributor meta."""
verbose_name: typing.ClassVar[str] = "Feed contributor"
verbose_name_plural: typing.ClassVar[str] = "Feed contributors"
db_table_comment: typing.ClassVar[str] = "Details about the contributor to a feed."
def __str__(self: Contributor) -> str:
"""Contributor."""
return f"{self.name} {self.email} {self.href}"
class Cloud(models.Model):
"""No one really knows what a cloud is.
Comes from the following elements:
- /rss/channel/cloud
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
domain = models.CharField(
max_length=255,
blank=True,
help_text="The domain of the cloud. Should be just the domain name, not including the http:// protocol. All clouds are presumed to operate over HTTP. The cloud specification does not support secure clouds over HTTPS, nor can clouds operate over other protocols.", # noqa: E501
)
port = models.CharField(
max_length=255,
blank=True,
help_text="The port of the cloud. Should be an integer, but Universal Feed Parser currently returns it as a string.", # noqa: E501
)
path = models.CharField(
max_length=255,
blank=True,
help_text="The URL path of the cloud.",
)
register_procedure = models.CharField(
max_length=255,
blank=True,
help_text="The name of the procedure to call on the cloud.",
)
protocol = models.CharField(
max_length=255,
blank=True,
help_text="The protocol of the cloud. Documentation differs on what the acceptable values are. Acceptable values definitely include xml-rpc and soap, although only in lowercase, despite both being acronyms. There is no way for a publisher to specify the version number of the protocol to use. soap refers to SOAP 1.1; the cloud interface does not support SOAP 1.0 or 1.2. post or http-post might also be acceptable values; nobody really knows for sure.", # noqa: E501
)
class Meta:
"""Cloud meta."""
verbose_name: typing.ClassVar[str] = "Feed cloud"
verbose_name_plural: typing.ClassVar[str] = "Feed clouds"
db_table_comment: typing.ClassVar[str] = "No one really knows what a cloud is."
def __str__(self: Cloud) -> str:
"""Cloud domain."""
return f"{self.register_procedure} {self.protocol}://{self.domain}{self.path}:{self.port}"
class Generator(models.Model):
"""Details about the software used to generate the feed.
Comes from the following elements:
- /atom03:feed/atom03:generator
- /atom10:feed/atom10:generator
- /rdf:RDF/rdf:channel/admin:generatorAgent/@rdf:resource
- /rss/channel/generator
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
name = models.TextField(blank=True, help_text="Human-readable name of the application used to generate the feed.")
# If this is a relative URI, it is resolved according to a set of rules:
# https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
href = models.URLField(
blank=True,
help_text="The URL of the application used to generate the feed.",
)
version = models.CharField(
max_length=255,
blank=True,
help_text="The version number of the application used to generate the feed. There is no required format for this, but most applications use a MAJOR.MINOR version number.", # noqa: E501
)
class Meta:
"""Generator meta."""
verbose_name: typing.ClassVar[str] = "Feed generator"
verbose_name_plural: typing.ClassVar[str] = "Feed generators"
db_table_comment: typing.ClassVar[str] = "Details about the software used to generate the feed."
def __str__(self: Generator) -> str:
"""Generator name."""
return f"{self.name} {self.version} {self.href}"
class Image(models.Model):
"""A feed image can be a logo, banner, or a picture of the author.
Comes from the following elements:
- /rdf:RDF/rdf:image
- /rss/channel/image
Example:
```xml
```
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
title = models.TextField(
blank=True,
help_text="The alternate text of the feed image, which would go in the alt attribute if you rendered the feed image as an HTML img element.", # noqa: E501
)
# If this is a relative URI, it is resolved according to a set of rules:
# https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
href = models.URLField(
blank=True,
help_text="The URL of the feed image itself, which would go in the src attribute if you rendered the feed image as an HTML img element.", # noqa: E501
)
# If this is a relative URI, it is resolved according to a set of rules:
# https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
link = models.URLField(
blank=True,
help_text="The URL which the feed image would point to. If you rendered the feed image as an HTML img element, you would wrap it in an a element and put this in the href attribute.", # noqa: E501
)
width = models.IntegerField(
default=0,
help_text="The width of the feed image, which would go in the width attribute if you rendered the feed image as an HTML img element.", # noqa: E501
)
height = models.IntegerField(
default=0,
help_text="The height of the feed image, which would go in the height attribute if you rendered the feed image as an HTML img element.", # noqa: E501
)
description = models.TextField(
blank=True,
help_text="A short description of the feed image, which would go in the title attribute if you rendered the feed image as an HTML img element. This element is rare; it was available in Netscape RSS 0.91 but was dropped from Userland RSS 0.91.", # noqa: E501
)
class Meta:
"""Image meta."""
verbose_name: typing.ClassVar[str] = "Feed image"
verbose_name_plural: typing.ClassVar[str] = "Feed images"
db_table_comment: typing.ClassVar[str] = "A feed image can be a logo, banner, or a picture of the author."
def __str__(self: Image) -> str:
"""Image title."""
return f"{self.title} {self.href} {self.link} {self.width}x{self.height} {self.description}"
class Info(models.Model):
"""Details about the feed.
Comes from the following elements:
- /atom03:feed/atom03:info
- /rss/channel/feedburner:browserFriendly
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# This element is generally ignored by feed readers.
# If this contains HTML or XHTML, it is sanitized by default.
# If this contains HTML or XHTML, certain (X)HTML elements within this value may
# contain relative URI (Uniform Resource Identifier)'s. These relative URI's are
# resolved according to a set of rules: https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
value = models.TextField(
blank=True,
help_text="Free-form human-readable description of the feed format itself. Intended for people who view the feed in a browser, to explain what they just clicked on.", # noqa: E501
)
# For Atom feeds, the content type is taken from the type attribute, which defaults to text/plain if not specified.
# For RSS feeds, the content type is auto-determined by inspecting the content, and defaults to text/html.
# Note that this may cause silent data loss if the value contains plain text with angle brackets.
# Future enhancement: some versions of RSS clearly specify that certain values default to text/plain,
# and Universal Feed Parser should respect this, but it doesn't yet.
info_type = models.CharField(
max_length=255,
blank=True,
help_text="The content type of the feed info. Most likely text/plain, text/html, or application/xhtml+xml.",
)
# Supposed to be a language code, but publishers have been known to publish random values like “English” or “German”. # noqa: E501
# May come from the element's xml:lang attribute, or it may inherit from a parent element's xml:lang, or the Content-Language HTTP header # noqa: E501
language = models.CharField(
max_length=255,
blank=True,
help_text="The language of the feed info.",
)
# is only useful in rare situations and can usually be ignored. It is the original base URI for this value, as
# specified by the element's xml:base attribute, or a parent element's xml:base, or the appropriate HTTP header,
# or the URI of the feed. By the time you see it, Universal Feed Parser has already resolved relative links in all
# values where it makes sense to do so. Clients should never need to manually resolve relative links.
base = models.URLField(
blank=True,
help_text="The original base URI for links within the feed copyright.",
)
class Meta:
"""Info meta."""
verbose_name: typing.ClassVar[str] = "Feed information"
verbose_name_plural: typing.ClassVar[str] = "Feed information"
db_table_comment: typing.ClassVar[str] = "Details about the feed."
def __str__(self: Info) -> str:
"""Info value."""
return f"{self.value} {self.info_type} {self.language} {self.base}"
class Link(models.Model):
"""A list of dictionaries with details on the links associated with the feed.
Each link has a rel (relationship), type (content type), and href (the URL that the link points to).
Some links may also have a title.
Comes from
/atom03:feed/atom03:link
/atom10:feed/atom10:link
/rdf:RDF/rdf:channel/rdf:link
/rss/channel/link
"""
# Atom 1.0 defines five standard link relationships and describes the process for registering others.
# Here are the five standard rel values:
# alternate
# enclosure
# related
# self
# via
rel = models.CharField(
max_length=255,
blank=True,
help_text="The relationship of this feed link.",
)
link_type = models.CharField(
max_length=255,
blank=True,
help_text="The content type of the page that this feed link points to.",
)
href = models.URLField(
blank=True,
help_text="The URL of the page that this feed link points to.",
)
title = models.TextField(
blank=True,
help_text="The title of this feed link.",
)
class Meta:
"""Link meta."""
verbose_name: typing.ClassVar[str] = "Feed link"
verbose_name_plural: typing.ClassVar[str] = "Feed links"
db_table_comment: typing.ClassVar[str] = (
"A list of dictionaries with details on the links associated with the feed."
)
def __str__(self: Link) -> str:
"""Link href."""
return f"{self.href}"
class Publisher(models.Model):
"""The publisher of the feed.
Comes from the following elements:
/rdf:RDF/rdf:channel/dc:publisher
/rss/channel/dc:publisher
/rss/channel/itunes:owner
/rss/channel/webMaster
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
name = models.TextField(blank=True, help_text="The name of this feed's publisher.")
email = models.EmailField(blank=True, help_text="The email address of this feed's publisher.")
# If this is a relative URI, it is resolved according to a set of rules:
# https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
href = models.URLField(
blank=True,
help_text="The URL of this feed's publisher. This can be the publisher's home page, or a contact page with a webmail form.", # noqa: E501
)
class Meta:
"""Publisher meta."""
verbose_name: typing.ClassVar[str] = "Feed publisher"
verbose_name_plural: typing.ClassVar[str] = "Feed publishers"
db_table_comment: typing.ClassVar[str] = "Details about the publisher of a feed."
def __str__(self: Publisher) -> str:
"""Publisher."""
return f"{self.name} {self.email} {self.href}"
class Rights(models.Model):
"""Details about the rights of a feed.
For machine-readable copyright information, see feed.license.
Comes from the following elements:
/atom03:feed/atom03:copyright
/atom10:feed/atom10:rights
/rdf:RDF/rdf:channel/dc:rights
/rss/channel/copyright
/rss/channel/dc:rights
"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# If this contains HTML or XHTML, it is sanitized by default.
# If this contains HTML or XHTML, certain (X)HTML elements within this value may
# contain relative URI (Uniform Resource Identifier)'s. These relative URI's are
# resolved according to a set of rules: https://feedparser.readthedocs.io/en/latest/resolving-relative-links.html#advanced-base
value = models.TextField(
blank=True,
help_text="A human-readable copyright statement for the feed.",
)
# For Atom feeds, the content type is taken from the type attribute, which defaults to text/plain if not specified.
# For RSS feeds, the content type is auto-determined by inspecting the content, and defaults to text/html.
# Note that this may cause silent data loss if the value contains plain text with angle brackets.
# Future enhancement: some versions of RSS clearly specify that certain values default to text/plain,
# and Universal Feed Parser should respect this, but it doesn't yet.
rights_type = models.CharField(
max_length=255,
blank=True,
help_text="The content type of the feed copyright. Most likely text/plain, text/html, or application/xhtml+xml.", # noqa: E501
)
# Supposed to be a language code, but publishers have been known to publish random values like “English” or “German”. # noqa: E501
# May come from the element's xml:lang attribute, or it may inherit from a parent element's xml:lang, or the Content-Language HTTP header # noqa: E501
language = models.CharField(
max_length=255,
blank=True,
help_text="The language of the feed rights.",
)
base = models.URLField(
blank=True,
help_text="The original base URI for links within the feed copyright.",
)
class Meta:
"""Rights meta."""
verbose_name: typing.ClassVar[str] = "Feed rights"
verbose_name_plural: typing.ClassVar[str] = "Feed rights"
db_table_comment: typing.ClassVar[str] = "Details about the rights of a feed."
def __str__(self: Rights) -> str:
"""Rights value."""
return f"{self.value} {self.rights_type} {self.language} {self.base}"
class Subtitle(models.Model):
"""A subtitle, tagline, slogan, or other short description of the feed.
Comes from
/atom03:feed/atom03:tagline
/atom10:feed/atom10:subtitle
/rdf:RDF/rdf:channel/dc:description
/rdf:RDF/rdf:channel/rdf:description
/rss/channel/dc:description
/rss/channel/description
/rss/channel/itunes:subtitle
"""
value = models.TextField(
blank=True,
help_text="A subtitle, tagline, slogan, or other short description of the feed.",
)
subtitle_type = models.CharField(
max_length=255,
blank=True,
help_text="The content type of the feed subtitle. Most likely text/plain, text/html, or application/xhtml+xml.",
)
language = models.CharField(
max_length=255,
blank=True,
help_text="The language of the feed subtitle.",
)
base = models.URLField(
blank=True,
help_text="The original base URI for links within the feed subtitle.",
)
class Meta:
"""Subtitle meta."""
verbose_name: typing.ClassVar[str] = "Feed subtitle"
verbose_name_plural: typing.ClassVar[str] = "Feed subtitles"
db_table_comment: typing.ClassVar[str] = "A subtitle, tagline, slogan, or other short description of the feed."
def __str__(self: Subtitle) -> str:
"""Subtitle value."""
return f"{self.value} {self.subtitle_type} {self.language} {self.base}"
class Tags(models.Model):
"""A list of tags associated with the feed.
Comes from
/atom03:feed/dc:subject
/atom10:feed/category
/rdf:RDF/rdf:channel/dc:subject
/rss/channel/category
/rss/channel/dc:subject
/rss/channel/itunes:category
/rss/channel/itunes:keywords
"""
term = models.TextField(
blank=True,
help_text="The category term (keyword).",
)
scheme = models.CharField(
blank=True,
max_length=255,
help_text="The category scheme (domain).",
)
label = models.TextField(
blank=True,
help_text="A human-readable label for the category.",
)
class Meta:
"""Tags meta."""
verbose_name: typing.ClassVar[str] = "Feed tag"
verbose_name_plural: typing.ClassVar[str] = "Feed tags"
db_table_comment: typing.ClassVar[str] = "A list of tags associated with the feed."
def __str__(self: Tags) -> str:
"""Tag term."""
return f"{self.term} {self.scheme} {self.label}"
class TextInput(models.Model):
"""A text input form. No one actually uses this. Why are you?
Comes from the following elements:
/rdf:RDF/rdf:textinput
/rss/channel/textInput
/rss/channel/textinput
Example:
This is a text input in a feed:
```xml