django-friendship: follows, blocks and bi-directional friendships as a Django app
Django app to manage following and bi-directional friendships
At a glance
- What is it?
- django-friendship is a reusable Django application that models friendship requests, follows and blocks with its own models, managers, template tags and signals. It fits Django projects that need a social graph on standard database tables, and it deliberately stops short of the parts a product would have to build, such as notifications.
- Who is it for?
- django-friendship suits Django teams whose users already live in a custom AUTH_USER_MODEL and who want friendship requests, follows and blocks on ordinary relational tables without designing a graph schema. It does not suit a project that needs a directed friendship graph, activity feeds or private messaging, because the data model stores relationships as model rows with a fixed set of query helpers.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three relationship types with one Django app
Any social feature needs to answer a handful of questions: who are my friends, who follows me, who do I follow, and who have I blocked. Writing those as ad-hoc models in every project repeats the same work and produces subtly different semantics each time.
django-friendship packages that work as a reusable Django application. It describes itself as enabling you to create and manage follows, blocks and bi-directional friendships between users, and the feature list names friendship request objects that can be accepted, rejected, canceled or marked as viewed, a blocklist per user, hooks to list sent and received requests filtered by status, template tags, and integration with AUTH_USER_MODEL.
The audience is Django developers adding a social layer to a site that already has users, and the assumptions are Django-shaped rather than graph-shaped. Relations are rows in ordinary tables queried through model managers. That keeps the data inspectable with the Django admin and queryable with the ORM, at the cost of the richer query patterns a graph store would offer.
Nine manager queries that answer the usual questions
The read side is a set of manager methods on three models: Friend, Follow and Block. The friend queries cover listing a user's friends, counting them, listing unread requests, listing unrejected requests, counting those unrejected requests, listing rejected requests, listing sent requests, testing whether two users are friends, and testing whether a request already exists in either direction.
Follow exposes followers and following. Block exposes blocked, which lists a user's blockers, blocking, which lists who that user blocks, and is_blocked for a pair test.
Two details are worth knowing before you write views. The request_exists check looks in both directions, so a pending inbound request counts as an existing request and you can use it to stop a user from sending a duplicate. And the distinction between unrejected and unread is separate: a request that was rejected still appears in the unrejected set only if it was not rejected, so if your UI shows a badge you want to reason about which of the two you actually want.
Every one of these calls takes a user argument, so a view typically passes request.user. The list of related strategies is configurable through FRIENDSHIP_MANAGER_FRIENDSHIP_REQUEST_SELECT_RELATED_STRATEGY, which can be select_related, prefetch_related or none, which matters when the default produces a query per relation in a list view.
Installing the app and making the first request
Installation is a pip install, an entry in INSTALLED_APPS and a migration:
pip install django-friendshippython manage.py migrateThe application name to add to INSTALLED_APPS is "friendship". You can then either use the manager API in your own views or include the built-in views through the URLconf:
urlpatterns = [
# other paths
path("friendship/", include("friendship.urls"))
]Sending a request needs the manager and the two users involved:
other_user = User.objects.get(pk=1)
Friend.objects.add_friend(
from_user=request.user, # The sender
to_user=other_user, # The recipient
message="Hi! I would like to add you", # optional
)The message argument is optional. On the receiving side, the recipient looks up the request object and acts on it:
from friendship.models import FriendshipRequest
friend_request = FriendshipRequest.objects.get(
from_user=request.user, to_user=other_user
)
friend_request.accept()
# or friend_request.reject()The accept and reject methods are the whole response flow, and the same object also exposes cancel and the viewed marker. Removing an established friendship is a manager call, Friend.objects.remove_friend(from_user=request.user, to_user=other_user).
Fourteen signals scoped by their sending model
The application emits its own signals from friendship.signals, which is the extension point for the parts a product has to add. The list covers friendship_request_created, friendship_request_rejected, friendship_request_canceled, friendship_request_viewed, friendship_request_accepted, friendship_removed, follower_created, follower_removed, followee_created, followee_removed, following_created, following_removed, block_created and block_removed.
Each signal's sender is the emitting model, so a receiver can be connected narrowly. The documented pattern imports the receiver decorator and the signal, then binds the signal to the emitting model with @receiver(follower_created, sender=Follow), where the sender is the Follow model and the receiver receives the follower plus keyword arguments.
This is where notification emails, activity log entries and feed fan-out belong, and the connection is to the model rather than to a string. Because the signals fire from inside the manager methods and the request methods, a receiver that raises an exception will interrupt the operation that triggered it, which is worth keeping in mind when you attach slow work such as sending mail.
Template tags that skip the view layer
For simple pages, the library ships template tags so a template can render a relationship list without a view that assembles it. The library is loaded with a single tag:
{% load friendshiptags %}And the available tags are:
{% friends request.user %}
{% followers request.user %}
{% following request.user %}
{% friend_requests request.user %}
{% blockers request.user %}
{% blocking request.user %}The tag names are singular while the outputs are lists, and the naming of the block tags is worth noting because it can read backwards at a glance: blockers lists the users who have blocked the given user, blocking lists the users that user has blocked. The same reversal appears in the manager methods, blocked and blocking.
Two settings shape what the tags and managers return. FRIENDSHIP_CONTEXT_OBJECT_NAME defaults to "user" and FRIENDSHIP_CONTEXT_OBJECT_LIST_NAME to "users", so those names are what a template context variable will be bound to when the tags render.
FRIENDSHIP_MAX_FRIENDS is checked at accept time
By default a user may have unlimited friends. Setting FRIENDSHIP_MAX_FRIENDS caps it, and the check happens when a request is accepted rather than when it is sent. If either of the two users is already at the limit, accept() raises MaxFriendsExceededError and no friendship is created.
FRIENDSHIP_MAX_FRIENDS = 800Leaving the setting unset means unlimited, so existing projects are unaffected by it, which makes it safe to add later. The exception type lives in friendship.exceptions, so a view that lets a user accept requests needs to catch MaxFriendsExceededError specifically rather than letting it surface as a server error.
One behaviour is easy to misread: a rejected request does not block future requests, so the sender may request again later. If your product needs a cooldown or a permanent block after rejection, that rule does not exist here. The blocklist is the separate mechanism for that, using Block.objects.add_block and Block.objects.remove_block.
Hand-rolled relation models as the alternative
The alternative to django-friendship is not another package but writing the same three models inside your own project. The difference in approach is where the work goes. Hand-written models let you store relationship rows in whatever shape your domain needs, including a directed friendship edge, a graph of followers of followers, or an edge type field for mutuals versus one-way links. You also keep full control over the database constraints.
What you give up is the query surface. The manager methods above exist because someone else already worked out the semantics of unrejected versus unread requests and of request_exists in both directions, and the signals exist because hooking the relationship events to your own code is the part every project needs and none wants to rewrite. The third option is to use the models but ignore the built-in views and URL include, which the project explicitly allows since the manager API is described as free to use in your own views.
The package also states that it performs validation to prevent common mistakes and uses caching for faster server response time, but the README does not describe which validations are enforced or how the cache is keyed, so you should check those in the documentation before relying on either.
A narrow dependency footprint and a fast moving support matrix
The packaging metadata lists no runtime dependencies at all, so django-friendship pulls nothing into your environment beyond what Django already provides. It requires Python 3.11 or later, is built with hatchling, and is published under BSD-3-Clause, with the classifier marking it as OSI Approved BSD License.
Support has moved quickly. Since v1.12.0 the requirements are Django 5.2, 6.0 and 6.1 with Python 3.11 through 3.15, including the free-threaded 3.14t and 3.15t builds. Earlier lines were broader: v1.10.0 covered Django 4.2, 5.0, 5.1, 5.2, 6.0 and 6.1 with Python 3.10 to 3.14, and v1.9.6 covered Django 4.2, 5.1 and 5.2 with Python 3.9 to 3.13.
The release list shows version 1.12.0 published on 2026-10-13, and the last push was on 2026-10-13. The repository is not archived. That combination means a Django upgrade can move you off a supported combination, so treat the version table as something to check during a framework upgrade rather than as a static fact.
Development runs through nox sessions driven by a justfile, with targets for bootstrap, test, lint, coverage, docs and release, and the test extras pull in pytest, pytest-django and factory-boy.
Editorial conclusion
django-friendship suits Django teams whose users already live in a custom AUTH_USER_MODEL and who want friendship requests, follows and blocks on ordinary relational tables without designing a graph schema. It does not suit a project that needs a directed friendship graph, activity feeds or private messaging, because the data model stores relationships as model rows with a fixed set of query helpers. Before adopting it, read FRIENDSHIP_MAX_FRIENDS in the settings documentation, confirm your Django version is on the supported list, and check how the blocklist interacts with your own queries before you expose a user-facing unfollow action.
Frequently asked questions
How do I install django-friendship in a Django project?
Run `pip install django-friendship`, add "friendship" to INSTALLED_APPS, and then run `python manage.py migrate`. You can use the manager API in your own views or include the built-in views with `path("friendship/", include("friendship.urls"))`.
How do I check whether two users are already friends in django-friendship?
Use `Friend.objects.are_friends(user1=request.user, user2=other_user)`, which returns True when the friendship exists. To check for a pending request in either direction, use `Friend.objects.request_exists(from_user=request.user, to_user=other_user)`.
Does django-friendship limit how many friends a user can have?
Only if you set FRIENDSHIP_MAX_FRIENDS. The default is unlimited, and the limit is checked when a request is accepted, where accept() raises MaxFriendsExceededError from friendship.exceptions if either user is already at the cap.
How do I react to friendship events in django-friendship?
Connect a receiver to the signals in friendship.signals, such as follower_created or friendship_request_accepted, and scope it with the sender argument, which is the emitting model. The documentation also points to the cookbook for task-oriented recipes.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/revsys-django-friendship)Was this useful?