Django-based system for creating and managing short links.
This system provides a way to create and manage short links, with an emphasis on custom paths. When a short link is accessed, the user is redirected to the full target URL. For example: https://our.short.domain/catchy_path -> https://our.full.domain/long_full_title_for_seo
The development environment requires:
- git (at least version 2)
- docker (current version recommended: 20.10.12)
- docker-compose (at least version 1.25.0; current recommended: 1.29.2)
The development database is a Docker container running PostgreSQL 16, which matches our deployment environment.
This uses Django 5.2, in a Debian 12 (Bookworm) container running Python 3.13. All code runs in the container, so local version of Python does not matter.
The container runs via docker_scripts/entrypoint.sh
, which
- Updates container with any new requirements, if the image hasn't been rebuilt (DEV environment only).
- Waits for the database to be completely available. This can take 5-10 seconds, depending on your hardware.
- Applies any pending migrations (DEV environment only).
- Creates a generic Django superuser, if one does not already exist (DEV environment only).
- Loads fixtures to populate lookup tables and to add a few sample records.
- Starts the Django application server.
-
Clone the repository.
$ git clone [email protected]:UCLALibrary/link-shortener.git
-
Change directory into the project.
$ cd link-shortener
-
Build using docker-compose.
$ docker-compose build
-
Bring the system up, with containers running in the background.
$ docker-compose up -d
-
Logs can be viewed, if needed (
-f
to tail logs).$ docker-compose logs -f db $ docker-compose logs -f django
-
Run commands in the containers, if needed.
# Open psql client in the dev database container $ docker-compose exec db psql -d link_shortener -U link_shortener # Open a shell in the django container $ docker-compose exec django bash # Django-aware Python shell $ docker-compose exec django python manage.py shell # Apply new migrations without a restart $ docker-compose exec django python manage.py migrate # Populate database with sample data (once it exists...) $ docker-compose exec django python manage.py loaddata --app shortlinks sample_data
-
Connect to the running application via browser
Application and Admin
-
Edit code locally. All changes are immediately available in the running container, but if a restart is needed:
$ docker-compose restart django
-
Shut down the system when done.
$ docker-compose down
Basic logging is available, with logs captured in logs/application.log
. At present, logs from both the custom application code and Django itself are captured.
Logging level is set to INFO
via .docker-compose_django.env
. If there's a regular need/desire for DEBUG level, we can discuss that.
Logging can be used in any Python file in the project. For example, in views.py
:
# Include the module with other imports
import logging
# Instantiate a logger, generally before any functions in the file
logger = logging.getLogger(__name__)
def my_view():
logger.info('This is a log message from my_view')
query_results = SomeModel.objects.all()
for r in query_results:
logger.info(f'{r.some_field=}')
try:
1/0
except Exception as e:
logger.exception('Example exception')
logger.debug('This DEBUG message only appears if DJANGO_LOG_LEVEL=DEBUG')
The current log format includes:
- Level: DEBUG, INFO, WARNING, ERROR, or CRITICAL
- Timestamp via
asctime
- Logger name: to distinguish between sources of messages (
django
vs the specific application) - Module: somewhat redundant with logger name
- Message: The main thing being logged
Local development environment: view logs/application.log
.
In deployed container:
/logs/
: see latest 200 lines of the log/logs/nnn
: see latestnnn
lines of the log
Tests focus on code which has significant side effects or implements custom logic.
Run tests in the container:
$ docker-compose exec django python manage.py test
Our deployment system is triggered by changes to the Helm chart. Typically, this is done by incrementing image:tag
(on or near line 9) in charts/prod-<appname></appname>-values.yaml
. We use a simple semantic versioning system:
- Bug fixes: update patch level (e.g.,
v1.0.1
tov1.0.2
) - Backward compatible functionality changes: update minor level (e.g.,
v1.0.1
tov1.1.0
) - Breaking changes: update major level (e.g.,
v1.0.1
tov2.0.0
)
In addition to updating version in the Helm chart, update the Release Notes in release_notes.html
. Put the latest changes first, following the established format.