Skip to content

Commit

Permalink
Add RSS related files from the django-lynx (private) repository.
Browse files Browse the repository at this point in the history
The code was taken from a working project which has been in production
for two and a half years. The code was refactored for this project, the
views, templates, etc. were moved to the demo app, requirements updated,
etc. etc.

THe demo site works and works with celery to automatically load feeds
according to a preset schedule. If you just want to see it in action
create an admin account, add a Source, then a Feed and you can load it
via an admin action.

Next steps are make everything run using either a virtualenv or Docker
and get it all polished for a first release.
  • Loading branch information
StuartMacKay committed Sep 23, 2023
1 parent deb5271 commit dd3ca48
Show file tree
Hide file tree
Showing 136 changed files with 5,755 additions and 303 deletions.
40 changes: 40 additions & 0 deletions .env.docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# Environment variables used for development using docker
#

# Copy this file to .env so it can be used by direnv and/or an IDE.

# The targets in the Makefile and the demo site use either a local virtualenv
# with locally installed services (database, message broker) or containers.
# This flag is used in the Makefile to define where everything lives so it
# can be used in either environment.

USE_DOCKER=1

# Service: db
# Rather than set POSTGRES_PASSWORD (required) and leave POSTGRES_USER and
# so POSTGRES_DB to default to "postgres" we set the variables to the name
# of the app so it mirrors the configuration for locally installed services
# which might be shared between sites. However, the value of doing this is
# that you easily initialize the database with dumps from production where
# the ownership of the tables is defined.

PGHOST=db
POSTGRES_DB=feeds
POSTGRES_USER=feeds
POSTGRES_PASSWORD=feeds

# Service: broker
# This, like the db service, simply mirrors the configuration for rabbitmq
# being installed locally and possibly shared between sites.

RABBITMQ_DEFAULT_USER=feeds
RABBITMQ_DEFAULT_PASS=feeds
RABBITMQ_DEFAULT_VHOST=feeds

# Service: web
# Environment variables used in Django settings

CELERY_BROKER_URL=amqp://feeds:feeds@broker:5672/feeds

DB_HOST=db
41 changes: 41 additions & 0 deletions .env.venv
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#
# Environment variables used for development using a virtualenv
#

# Copy this file to .env so it can be used by direnv and/or an IDE.

# This assumes you've installed postgresql and rabbitmq on your
# machine and have added the accounts need to connect to them:
#
# sudo -u postgres psql
# postgres=# create database feeds;
# postgres=# create user feeds with encrypted password 'feeds';
# postgres=# grant all privileges on database feeds to feeds;
#
# sudo rabbitmqctl add_user feeds feeds
# sudo rabbitmqctl set_permissions -p / feeds ".*" ".*" ".*"
# sudo rabbitmqctl add_vhost feeds

# Set environment variables used by the postgresql commands to connect
# to the database. This is the admin account not the account used by
# Django to connect to the database. PGPASSWORD is particularly useful
# to avoid having to enter it for each command. You will need to configure
# the server to allow username/password (md5) authentication on local
# (socket) connections and so avoid having to su to the postgres user first.
#
# /etc/postgresql/<version>/main/pg_hba.conf
# local all all md5
#
# For more info on environment variables see,
# https://www.postgresql.org/docs/current/libpq-envars.html

PGHOST=localhost
PGPORT=5432
PGUSER=postgres
PGPASSWORD=postgres

# Environment variables used by Django

CELERY_BROKER_URL=amqp://feeds:feeds@localhost:5672/feeds

DB_HOST=localhost
12 changes: 12 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# direnv file
# When you cd to this directory, direnv will activate the virtualenv
# and set all the environment variables in the .env file.
# See https://direnv.net/man/direnv-stdlib.1.html

# Activate the virtualenv
# This simply replicates what venv/bin/activate does.
export VIRTUAL_ENV=venv
PATH_add "$VIRTUAL_ENV/bin"

# Set environment variables from .env
dotenv
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ __pycache__/
# Ignore dot files except those required for the project
.*
!.editorconfig
!.envrc
!.env.docker
!.env.venv
!.flake8
!.gitattributes
!.github
Expand Down
36 changes: 36 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#
# Dockerfile: configuration for building the images for the web app,
# celery-beat and celery-worker.
#

# Base python image
FROM python:3.8.16-buster
# Add the site to the python path
ENV PYTHONPATH /code
# Send all output on stdout and stderr straight to the container logs
ENV PYTHONUNBUFFERED 1
# Set the locale
ENV LC_ALL C.UTF-8
# Terminals support 256 colours
ENV TERM xterm-256color

# Mount the site directory

RUN mkdir /code
WORKDIR /code

# Install dependencies

RUN apt-get update \
&& apt-get install -y --no-install-recommends

RUN apt-get install -y --no-install-recommends \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install requirements

COPY requirements/dev.txt /tmp/requirements.txt
RUN pip install --upgrade setuptools pip wheel\
&& pip install pip-tools==6.10.0\
&& pip install -r /tmp/requirements.txt
6 changes: 2 additions & 4 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ include CHANGELOG.rst
exclude setup.cfg
exclude pyproject.toml

recursive-include src/app_project/locale *
recursive-include src/app_project/templates *
recursive-include src/app_project/static *
recursive-include src/feeds/ *

recursive-exclude src/app_project/tests *
recursive-exclude src/feeds/tests *

recursive-exclude * .cache
recursive-exclude * __pycache__
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ clean: clean-venv clean-build clean-tests clean-mypy clean-coverage clean-docs
$(venv_dir):
$(site_python) -m venv $(venv_dir)
$(pip) install --upgrade pip setuptools wheel
$(pip) install pip-tools==6.10.0
$(pip) install pip-tools

requirements/dev.txt: requirements/dev.in requirements/docs.in requirements/tests.in
$(pip-compile) requirements/dev.in
Expand Down Expand Up @@ -212,7 +212,7 @@ checks: flake8 black isort mypy

.PHONY: coverage
coverage:
$(pytest) --cov=app_project --cov-config=setup.cfg --cov-report html
$(pytest) --cov=feeds --cov-config=setup.cfg --cov-report html

.PHONY: test
test:
Expand Down
68 changes: 15 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,26 @@
# Django App Project
# Django Feeds

[![Build Status](https://img.shields.io/github/actions/workflow/status/StuartMacKay/django-app-template/ci.yml?branch=master)](https://github.com/StuartMacKay/django-app-template/actions/workflows/ci.yml?query=branch%3Amaster)
[![Build Status](https://img.shields.io/github/actions/workflow/status/StuartMacKay/django-feeds/ci.yml?branch=master)](https://github.com/StuartMacKay/django-feeds/actions/workflows/ci.yml?query=branch%3Amaster)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)

## Features
Django Feeds is an aggregator for RSS and Atom feeds.

* Development with [black](https://github.com/psf/black) so everybody gets the code formatting rules they deserve
* Development with [flake8](https://flake8.pycqa.org/en/latest/) so people using ed get syntax checking
* Development with [isort](https://pycqa.github.io/isort/) for automatically sorting imports
* Development with [mypy](https://mypy-lang.org/) for type-hinting to catch errors
* Testing with [pytest](https://docs.pytest.org/), [FactoryBoy](https://factoryboy.readthedocs.io/en/stable/) and [tox](https://tox.wiki/en/latest/)
* Manage versions with [bump2version](https://pypi.org/project/bump2version/) - for semantic version numbers
* Manage dependency versions with [pip-tools](https://github.com/jazzband/pip-tools)
* Manage dependency upgrades with [pip-upgrade](https://github.com/simion/pip-upgrader)
## Features

## Quick start
## Quick Start

First, download and unzip the project files in the directory of your choice.
Then rename the project to something more useful:
```shell
mv django-app-template django-myapp
```
## Demo

Change to the project directory and start setting things up:
```shell
cd django-myapp
```
If you clone or download the [django-feeds](https://github.com/StuartMacKay/django-feeds)
repository there is a demonstration application that lets you see how it
all works.

First, build the virtualenv and install all the dependencies. This will
also build the library:
```shell
git clone [email protected]:StuartMacKay/django-feeds.git
cd django-feeds
make install
```

Now run the demo site:
```shell
make demo
```


Run the database migrations:
```shell
./manage.py migrate
```

Run the tests:
```shell
make tests
```

Run the django server:
```shell
./manage.py runserver
```

Open a browser and visit http://localhost:8000 and, voila, we have a working
site. Well cover the deployment later.

Almost all steps used the project Makefile. That's great if you're running
Linux with GNU Make and not much fun if you're not. All is not lost, however.
All the Makefile's targets contain only one or two commands, so even if you
are running Windows you should still be able to get the site running without
too much effort.
```
It's a standard django project so if you don't have `make` available
then just look at the [Makefile](Makefile) and run the commands from
the various targets.
19 changes: 19 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
# TODO

The code runs and runs well. It has been in production for almost two
years, so it is rather polished. It was factored out of the
[Voices for Independence](https://www.voices.scot) web site with s small
number of changes and is now being repackaged to make it reusable in other
projects we have in mind.

* replace the original bootstrap css classes with semantic names, so it
can be restyled easily.

* move 'business logic' out of the views and out of the querysets into a
services layer - an internal API. That way you're not forced
to subclass the views, and you can easily write your own.

* Add more blocks to the templates and create more snippets so again you
can easily replace what is provided in the app.

* Also load the feeds using cron, so you are not forced to use celery.

* Add a REST API - at some point.
1 change: 1 addition & 0 deletions demo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .celery import app as celery_app
5 changes: 5 additions & 0 deletions demo/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class Config(AppConfig):
name = "demo"
2 changes: 1 addition & 1 deletion demo/conf/asgi.py → demo/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@

from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.conf.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")

application = get_asgi_application()
73 changes: 73 additions & 0 deletions demo/celery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import logging
import os

import structlog
from celery import Celery # type: ignore
from celery.schedules import crontab
from celery.signals import setup_logging # type: ignore
from django_structlog.celery.steps import DjangoStructLogInitStep # type: ignore

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")

app = Celery()

# Load the configuration
app.config_from_object("demo.settings")
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()
# Configure workers to use structlog
app.steps["worker"].add(DjangoStructLogInitStep)

# A celery task runs every hour, on the error and checked the schedule for
# each active feed. If the schedule, also a crontab, matches the current
# time then the feed is loaded. This two-level approach comes in useful for
# feeds that are either updated in frequently, so there's no need to cook
# the planet by checking every hour, or, dealing with throttling by services
# such as Cloudflare, where too many request s

FEEDS_TASK_SCHEDULE = os.environ.get("FEEDS_TASK_SCHEDULE", "0 * * * *")
minute, hour, day_of_week, day_of_month, month_of_year = FEEDS_TASK_SCHEDULE.split()

app.conf.beat_schedule = {
"load-feeds": {
"task": "demo.tasks.load_feeds",
"schedule": crontab(
minute=minute,
hour=hour,
day_of_week=day_of_week,
day_of_month=day_of_month,
month_of_year=month_of_year,
),
},
"daily-digest": {
"task": "feed.tasks.daily_digest",
"schedule": crontab(minute="0", hour="2"), # 2am, each day
},
}


@setup_logging.connect
def receiver_setup_logging(loglevel, logfile, format, colorize, **kwargs): # noqa
from django.conf import settings

config = settings.LOGGING
logging.config.dictConfig(config) # type: ignore

# noinspection DuplicatedCode
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
],
context_class=structlog.threadlocal.wrap_dict(dict),
logger_factory=structlog.stdlib.LoggerFactory(),
wrapper_class=structlog.stdlib.BoundLogger, # type: ignore
cache_logger_on_first_use=True,
)
17 changes: 0 additions & 17 deletions demo/conf/urls.py

This file was deleted.

Loading

0 comments on commit dd3ca48

Please sign in to comment.