Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make your library a little better #14

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ coverage.xml

# Django stuff:
*.log
django*

# Sphinx documentation
docs/_build/
Expand Down
214 changes: 131 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,131 @@
This django app intended for writing HTTP log to database and/or watch last user activity.

Features:
- DB router for writing logs to another database.
- Filters for ignoring some queries by URL, HTTP methods and response codes.
- Saving anonymous activity as fake user.
- Autocreation log DB (for postgresql)

Install:

$ pip install django-user-activity-log

settings.py:


```python
INSTALLED_APPS = (
...
'activity_log',
)

MIDDLEWARE_CLASSES = (
...
'activity_log.middleware.ActivityLogMiddleware',
)

# For writing log to another DB

DATABASE_ROUTERS = ['activity_log.router.DatabaseAppsRouter']
DATABASE_APPS_MAPPING = {'activity_log': 'logs'}

# If you set up DATABASE_APPS_MAPPING, but don't set related value in
# DATABASES, it will created automatically using "default" DB settings
# as example.
DATABASES = {
'logs': {
...
},
}

# Create DB automatically (for postgres, and may be mysql).
# We create log database automatically using raw SQL in pre_migrate signal.
# You must insure, that DB user has permissions for creation databases.
# Tested only for postgresql
ACTIVITYLOG_AUTOCREATE_DB = False

# App settings

# Log anonimus actions?
ACTIVITYLOG_ANONIMOUS = True

# Update last activity datetime in user profile. Needs updates for user model.
ACTIVITYLOG_LAST_ACTIVITY = True

# Only this methods will be logged
ACTIVITYLOG_METHODS = ('POST', 'GET')

# List of response statuses, which logged. By default - all logged.
# Don't use with ACTIVITYLOG_EXCLUDE_STATUSES
ACTIVITYLOG_STATUSES = (200, )

# List of response statuses, which ignores. Don't use with ACTIVITYLOG_STATUSES
# ACTIVITYLOG_EXCLUDE_STATUSES = (302, )

# URL substrings, which ignores
ACTIVITYLOG_EXCLUDE_URLS = ('/admin/activity_log/activitylog', )
```

account/models.py:

```python
from django.contrib.auth.models import AbstractUser
from activity_log.models import UserMixin

# Only for LAST_ACTIVITY = True
class User(AbstractUser, UserMixin):
pass
```

$ python manage.py migrate & python manage.py migrate --database=logs

If you use ACTIVITYLOG_AUTOCREATE_DB migrations to logs database
will be run automatically.
# django-activity-log

Forked from :
[scailer/django-user-activity-log](https://github.com/scailer/django-user-activity-log)
__________________________________________________________
## Owner's Expressions :
This django app intended for writing HTTP log to database and/or watch last user activity.

Features:
- DB router for writing logs to another database.
- Filters for ignoring some queries by URL, HTTP methods and response codes.
- Saving anonymous activity as fake user.
- Autocreation log DB (for postgresql)

Install:

[deprecated]:

$ pip install django-user-activity-log

[new library]:
```
pip install django-activity-log
```

settings.py:


```python
INSTALLED_APPS = (
...
'activity_log',
)

MIDDLEWARE_CLASSES = (
...
'activity_log.middleware.ActivityLogMiddleware',
)

# For writing log to another DB

DATABASE_ROUTERS = ['activity_log.router.DatabaseAppsRouter']
DATABASE_APPS_MAPPING = {'activity_log': 'logs'}

# If you set up DATABASE_APPS_MAPPING, but don't set related value in
# DATABASES, it will created automatically using "default" DB settings
# as example.
DATABASES = {
'logs': {
...
},
}

# Create DB automatically (for postgres, and may be mysql).
# We create log database automatically using raw SQL in pre_migrate signal.
# You must insure, that DB user has permissions for creation databases.
# Tested only for postgresql
ACTIVITYLOG_AUTOCREATE_DB = False

# App settings

# Log anonimus actions?
ACTIVITYLOG_ANONIMOUS = True

# Update last activity datetime in user profile. Needs updates for user model.
ACTIVITYLOG_LAST_ACTIVITY = True

# Only this methods will be logged
ACTIVITYLOG_METHODS = ('POST', 'GET')

# List of response statuses, which logged. By default - all logged.
# Don't use with ACTIVITYLOG_EXCLUDE_STATUSES
ACTIVITYLOG_STATUSES = (200, )

# List of response statuses, which ignores. Don't use with ACTIVITYLOG_STATUSES
# ACTIVITYLOG_EXCLUDE_STATUSES = (302, )

# URL substrings, which ignores
ACTIVITYLOG_EXCLUDE_URLS = ('/admin/activity_log/activitylog', )
```

account/models.py:

```python
from django.contrib.auth.models import AbstractUser
from activity_log.models import UserMixin

# Only for LAST_ACTIVITY = True
class User(AbstractUser, UserMixin):
pass
```

$ python manage.py migrate & python manage.py migrate --database=logs

If you use ACTIVITYLOG_AUTOCREATE_DB migrations to logs database
will be run automatically.

__________________________________________________________
## Changelogs of this fork :

#### 1. ```ACTIVITYLOG_MAIN_IP_KEY_VALUE```:
You can set this string in the settings.py file. When you have a specific CDN or there are changes in headers of requests from the user on the frontend side, this key value takes the highest priority to set the IP address from the headers.

#### 2. ```ACTIVITYLOG_MAXIMUM_RECORD_SIZE```:
You can set this integer in the settings.py file.
This constraint controls the number of saved records by removing the oldest records.

#### 3. ```EXCLUDE_IP_LIST```:
You can set this list in the settings.py file.
This is a list of IP addresses that you do not want to log.

#### 4. ```IP_ADDRESS_HEADERS```:
I changed the priority to find the IP address better. When you have a CDN, the previous library saves the IP address of the CDN, which is not useful.

#### 5. ```headers```:
There is a new field in the changelog model that saves headers of requests from the user as a pretty string.

#### 6. ```payload```:
There is a new field in the changelog model that saves the payload of requests from the user as a pretty string.

#### 7. Test on django version 4.0.1:
It works well with Django version 4.0.1.

#### 8. change migration files:
Delete old migrations and create one file to migrate models of this library.

#### 9. IP Management:
In admin panel , Model BlackListIPAdress you can archive or block every ip address would you like with its network address or not.

Repository :
[HosseinSayyedMousavi/django-user-activity-log](https://github.com/HosseinSayyedMousavi/django-user-activity-log/)
9 changes: 9 additions & 0 deletions activity_log/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,13 @@ class LogAdmin(admin.ModelAdmin):
search_fields = ('user', 'request_url')


class BlackListAdmin(admin.ModelAdmin):
list_display = ('id' , 'ip_address', 'block_network_address','blocked')
list_editable = ('ip_address', 'block_network_address','blocked')
list_display_links = ('id',)
list_filter = ('block_network_address','blocked')
search_fields = list_display_links

admin.site.register(models.BlackListIPAdress,BlackListAdmin)

admin.site.register(models.ActivityLog, LogAdmin)
18 changes: 14 additions & 4 deletions activity_log/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
from django.conf import settings


# Log anonimus actions?
ANONIMOUS = getattr(settings, 'ACTIVITYLOG_ANONIMOUS', True)
# Log anonymous actions?
ACTIVITYLOG_MAIN_IP_KEY_VALUE = getattr(settings, 'ACTIVITYLOG_MAIN_IP_KEY_VALUE', "MAIN_IP")
ANONIMOUS = getattr(settings, 'ACTIVITYLOG_ANONIMOUS', False)
# Ensure misspelling from previous versions is handled
if ANONIMOUS:
ANONYMOUS = True
else:
ANONYMOUS = getattr(settings, 'ACTIVITYLOG_ANONYMOUS', True)

# Update last activity datetime in user profile
LAST_ACTIVITY = getattr(settings, 'ACTIVITYLOG_LAST_ACTIVITY', True)
Expand All @@ -25,13 +31,15 @@
# URL substrings, which ignores
EXCLUDE_URLS = getattr(settings, 'ACTIVITYLOG_EXCLUDE_URLS', ())

# IPs substrings, which ignores
EXCLUDE_IP_LIST = getattr(settings, 'EXCLUDE_IP_LIST', [])
# Create DB automatically (for postgres, and may be mysql)
AUTOCREATE_DB = getattr(settings, 'ACTIVITYLOG_AUTOCREATE_DB', False)

# Customization for ip_address field.
# List of HTTP headers where we will search user IP
IP_ADDRESS_HEADERS = ('HTTP_X_REAL_IP', 'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR')
IP_ADDRESS_HEADERS = (ACTIVITYLOG_MAIN_IP_KEY_VALUE,'HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP', 'HTTP_CLIENT_IP',
'REMOTE_ADDR')

# Customization for extra_data field.
# Function get request and response objects, returns str
Expand All @@ -44,3 +52,5 @@
db = settings.DATABASES['default'].copy()
db['NAME'] = '{}_{}'.format(db['NAME'], LOG_DB_KEY)
settings.DATABASES[LOG_DB_KEY] = db

ACTIVITYLOG_MAXIMUM_RECORD_SIZE = getattr(settings, 'ACTIVITYLOG_MAXIMUM_RECORD_SIZE', None)
42 changes: 35 additions & 7 deletions activity_log/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,45 @@
from django.utils.module_loading import import_string as _load
from django.core.exceptions import DisallowedHost
from django.http import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
from .models import ActivityLog
from . import conf

from django.utils.encoding import force_str
import json
import pprintpp
from .models import BlackListIPAdress
from django.db.models import Q
import re

def get_ip_address(request):
for header in conf.IP_ADDRESS_HEADERS:
addr = request.META.get(header)
if addr:
return addr.split(',')[0].strip()

def get_META_headers(request):
# Read the content from the LimitedStream
content = request.META.read()

# Decode the content using the appropriate encoding
decoded_content = force_str(content, encoding='utf-8')

# Convert the decoded content to a Python object (e.g., a dictionary)
data = json.loads(decoded_content)

# Return the data as JSON
return json.dumps(data)

def get_extra_data(request, response, body):
if not conf.GET_EXTRA_DATA:
return
return _load(conf.GET_EXTRA_DATA)(request, response, body)


class ActivityLogMiddleware:
class ActivityLogMiddleware(MiddlewareMixin):
def process_request(self, request):
request.saved_body = request.body
if conf.LAST_ACTIVITY and request.user.is_authenticated():
if conf.LAST_ACTIVITY and request.user.is_authenticated:
getattr(request.user, 'update_last_activity', lambda: 1)()

def process_response(self, request, response):
Expand All @@ -37,7 +55,7 @@ def process_response(self, request, response):

def _write_log(self, request, response, body):
miss_log = [
not(conf.ANONIMOUS or request.user.is_authenticated()),
not(conf.ANONYMOUS or request.user.is_authenticated),
request.method not in conf.METHODS,
any(url in request.path for url in conf.EXCLUDE_URLS)
]
Expand All @@ -51,10 +69,10 @@ def _write_log(self, request, response, body):
if any(miss_log):
return

if getattr(request, 'user', None) and request.user.is_authenticated():
if getattr(request, 'user', None) and request.user.is_authenticated:
user, user_id = request.user.get_username(), request.user.pk
elif getattr(request, 'session', None):
user, user_id = 'anon_{}'.format(request.session.session_key), 0
user, user_id = 'unknown_{}'.format(request.session.session_key), 0
else:
return

Expand All @@ -65,5 +83,15 @@ def _write_log(self, request, response, body):
request_method=request.method,
response_code=response.status_code,
ip_address=get_ip_address(request),
extra_data=get_extra_data(request, response, body)
extra_data=get_extra_data(request, response, body),
headers = pprintpp.pformat(dict(request.META.items()),indent=4),
payload = request.body
)
def __call__(self, request):
ip_address = get_ip_address(request)
network_address =re.findall(r"([\.\d]+)\.",ip_address)[0]
query = Q(block_network_address=True , ip_address__startswith = network_address , blocked = True) | Q(ip_address=ip_address , blocked = True)
if BlackListIPAdress.objects.filter(query).exists() :
return HttpResponseForbidden()

return super().__call__(request)
Loading