diff --git a/euscanwww/djeuscan/feeds.py b/euscanwww/djeuscan/feeds.py index dfc4268..9306bcb 100644 --- a/euscanwww/djeuscan/feeds.py +++ b/euscanwww/djeuscan/feeds.py @@ -9,7 +9,7 @@ from django.db.models import Q from euscan.version import gentoo_unstable from djeuscan.models import Package, Herd, Maintainer, VersionLog -from djeuscan.helpers import get_profile, get_account_packages +from djeuscan.helpers import get_profile, get_account_versionlogs class BaseFeed(Feed): @@ -230,9 +230,6 @@ class UserFeed(BaseFeed): user, options = data["user"], data["options"] profile = get_profile(user) - packages = get_account_packages(user) - overlays = [o.name for o in profile.overlays.all()] + vlogs = get_account_versionlogs(profile) - return VersionLog.objects.filter( - Q(package__in=packages) | Q(overlay__in=overlays) - ), 100 + return vlogs, 100 diff --git a/euscanwww/djeuscan/forms.py b/euscanwww/djeuscan/forms.py index 2b1e238..95f4717 100644 --- a/euscanwww/djeuscan/forms.py +++ b/euscanwww/djeuscan/forms.py @@ -1,6 +1,6 @@ from django import forms -from djeuscan.models import Version, ProblemReport +from djeuscan.models import Version, ProblemReport, UserProfile class WorldForm(forms.Form): @@ -52,6 +52,10 @@ class PreferencesForm(forms.Form): email_activated = forms.BooleanField( required=False, label="Receive euscan emails" ) + email_every = forms.ChoiceField( + choices=UserProfile.EMAIL_OPTS, + label="Send email", + ) email_ignore_pre = forms.BooleanField( required=False, label="Ignore unstable releases" ) diff --git a/euscanwww/djeuscan/helpers.py b/euscanwww/djeuscan/helpers.py index abfa7f5..e502a2c 100644 --- a/euscanwww/djeuscan/helpers.py +++ b/euscanwww/djeuscan/helpers.py @@ -108,17 +108,21 @@ def get_account_maintainers(user): return Package.objects.maintainers(ids=ids) -def get_account_packages(user): +def get_account_versionlogs(profile): """ Returns all watched packages """ - from djeuscan.models import Package + from djeuscan.models import Package, VersionLog - profile = get_profile(user) q_categories = Q(category__in=[ category.name for category in profile.categories.all()]) q_herds = Q(herds__in=profile.herds.all()) q_maintainers = Q(maintainers__in=profile.maintainers.all()) packages = list(profile.packages.all()) + list(Package.objects.filter( q_categories | q_herds | q_maintainers)) - return packages + + overlays = [o.name for o in profile.overlays.all()] + + return VersionLog.objects.filter( + Q(package__in=packages) | Q(overlay__in=overlays) + ) diff --git a/euscanwww/djeuscan/migrations/0015_initial_celery_periodictasks.py b/euscanwww/djeuscan/migrations/0015_initial_celery_periodictasks.py index 721fc2f..c25d411 100644 --- a/euscanwww/djeuscan/migrations/0015_initial_celery_periodictasks.py +++ b/euscanwww/djeuscan/migrations/0015_initial_celery_periodictasks.py @@ -23,6 +23,12 @@ class Migration(DataMigration): day_of_month="*", month_of_year="*" ) + every_month = orm["djcelery.CrontabSchedule"].objects.create( + minute="00", + hour="05", + day_of_month="1", + month_of_year="*" + ) orm["djcelery.PeriodicTask"].objects.create( name="Daily portage update", task="djeuscan.tasks.update_portage", @@ -33,6 +39,16 @@ class Migration(DataMigration): task="djeuscan.tasks.update_upstream", crontab=every_week ) + orm["djcelery.PeriodicTask"].objects.create( + name="Weekly emails", + task="djeuscan.tasks.send_weekly_email", + crontab=every_week + ) + orm["djcelery.PeriodicTask"].objects.create( + name="Monthly emails", + task="djeuscan.tasks.send_monthly_email", + crontab=every_month + ) def backwards(self, orm): orm["djcelery.CrontabSchedule"].objects.all().delete() diff --git a/euscanwww/djeuscan/migrations/0020_auto__add_field_userprofile_email_every.py b/euscanwww/djeuscan/migrations/0020_auto__add_field_userprofile_email_every.py new file mode 100644 index 0000000..c0f1525 --- /dev/null +++ b/euscanwww/djeuscan/migrations/0020_auto__add_field_userprofile_email_every.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'UserProfile.email_every' + db.add_column('djeuscan_userprofile', 'email_every', + self.gf('django.db.models.fields.IntegerField')(default=1), + keep_default=False) + + def backwards(self, orm): + # Deleting field 'UserProfile.email_every' + db.delete_column('djeuscan_userprofile', 'email_every') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'djeuscan.category': { + 'Meta': {'object_name': 'Category'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + 'djeuscan.categorylog': { + 'Meta': {'object_name': 'CategoryLog', '_ormbases': ['djeuscan.Log']}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'djeuscan.euscanresult': { + 'Meta': {'object_name': 'EuscanResult'}, + 'datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'ebuild': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'result': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'scan_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'djeuscan.herd': { + 'Meta': {'object_name': 'Herd'}, + 'email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'herd': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Maintainer']", 'symmetrical': 'False'}) + }, + 'djeuscan.herdlog': { + 'Meta': {'object_name': 'HerdLog', '_ormbases': ['djeuscan.Log']}, + 'herd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Herd']"}), + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'djeuscan.log': { + 'Meta': {'object_name': 'Log'}, + 'datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'n_packages_gentoo': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_packages_outdated': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_packages_overlay': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions_gentoo': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions_overlay': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions_upstream': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'djeuscan.maintainer': { + 'Meta': {'object_name': 'Maintainer'}, + 'email': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djeuscan.maintainerlog': { + 'Meta': {'object_name': 'MaintainerLog', '_ormbases': ['djeuscan.Log']}, + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}), + 'maintainer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Maintainer']"}) + }, + 'djeuscan.overlay': { + 'Meta': {'object_name': 'Overlay'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + 'djeuscan.package': { + 'Meta': {'unique_together': "(['category', 'name'],)", 'object_name': 'Package'}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'herds': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Herd']", 'symmetrical': 'False', 'blank': 'True'}), + 'homepage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_version_gentoo': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_version_gentoo'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['djeuscan.Version']"}), + 'last_version_overlay': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_version_overlay'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['djeuscan.Version']"}), + 'last_version_upstream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_version_upstream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['djeuscan.Version']"}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Maintainer']", 'symmetrical': 'False', 'blank': 'True'}), + 'n_overlay': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_packaged': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djeuscan.problemreport': { + 'Meta': {'object_name': 'ProblemReport'}, + 'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Version']", 'null': 'True', 'blank': 'True'}) + }, + 'djeuscan.refreshpackagequery': { + 'Meta': {'object_name': 'RefreshPackageQuery'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) + }, + 'djeuscan.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Category']", 'symmetrical': 'False'}), + 'email_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'email_every': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'email_ignore_pre': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_ignore_pre_if_stable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_ignore_pre': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_ignore_pre_if_stable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_portage_info': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_show_adds': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'feed_show_removals': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'feed_upstream_info': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'herds': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Herd']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Maintainer']", 'symmetrical': 'False'}), + 'overlays': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Overlay']", 'symmetrical': 'False'}), + 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Package']", 'symmetrical': 'False'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'djeuscan.version': { + 'Meta': {'unique_together': "(['package', 'slot', 'revision', 'version', 'overlay'],)", 'object_name': 'Version'}, + 'alive': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'confidence': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'ebuild_path': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'handler': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_path': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'overlay': ('django.db.models.fields.CharField', [], {'default': "'gentoo'", 'max_length': '128', 'db_index': 'True', 'blank': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'packaged': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'slot': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'urls': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'vtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}) + }, + 'djeuscan.versionlog': { + 'Meta': {'object_name': 'VersionLog'}, + 'action': ('django.db.models.fields.IntegerField', [], {}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'overlay': ('django.db.models.fields.CharField', [], {'default': "'gentoo'", 'max_length': '128', 'blank': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'packaged': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'slot': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'vtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}) + }, + 'djeuscan.worldlog': { + 'Meta': {'object_name': 'WorldLog', '_ormbases': ['djeuscan.Log']}, + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['djeuscan'] \ No newline at end of file diff --git a/euscanwww/djeuscan/migrations/0021_auto__add_field_userprofile_last_email.py b/euscanwww/djeuscan/migrations/0021_auto__add_field_userprofile_last_email.py new file mode 100644 index 0000000..5a2e9dd --- /dev/null +++ b/euscanwww/djeuscan/migrations/0021_auto__add_field_userprofile_last_email.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'UserProfile.last_email' + db.add_column('djeuscan_userprofile', 'last_email', + self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default=datetime.datetime(2012, 8, 11, 0, 0), blank=True), + keep_default=False) + + def backwards(self, orm): + # Deleting field 'UserProfile.last_email' + db.delete_column('djeuscan_userprofile', 'last_email') + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'djeuscan.category': { + 'Meta': {'object_name': 'Category'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + 'djeuscan.categorylog': { + 'Meta': {'object_name': 'CategoryLog', '_ormbases': ['djeuscan.Log']}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'djeuscan.euscanresult': { + 'Meta': {'object_name': 'EuscanResult'}, + 'datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'ebuild': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'result': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'scan_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) + }, + 'djeuscan.herd': { + 'Meta': {'object_name': 'Herd'}, + 'email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + 'herd': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Maintainer']", 'symmetrical': 'False'}) + }, + 'djeuscan.herdlog': { + 'Meta': {'object_name': 'HerdLog', '_ormbases': ['djeuscan.Log']}, + 'herd': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Herd']"}), + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}) + }, + 'djeuscan.log': { + 'Meta': {'object_name': 'Log'}, + 'datetime': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'n_packages_gentoo': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_packages_outdated': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_packages_overlay': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions_gentoo': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions_overlay': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions_upstream': ('django.db.models.fields.IntegerField', [], {'default': '0'}) + }, + 'djeuscan.maintainer': { + 'Meta': {'object_name': 'Maintainer'}, + 'email': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djeuscan.maintainerlog': { + 'Meta': {'object_name': 'MaintainerLog', '_ormbases': ['djeuscan.Log']}, + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}), + 'maintainer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Maintainer']"}) + }, + 'djeuscan.overlay': { + 'Meta': {'object_name': 'Overlay'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}) + }, + 'djeuscan.package': { + 'Meta': {'unique_together': "(['category', 'name'],)", 'object_name': 'Package'}, + 'category': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'herds': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Herd']", 'symmetrical': 'False', 'blank': 'True'}), + 'homepage': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_version_gentoo': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_version_gentoo'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['djeuscan.Version']"}), + 'last_version_overlay': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_version_overlay'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['djeuscan.Version']"}), + 'last_version_upstream': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_version_upstream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['djeuscan.Version']"}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Maintainer']", 'symmetrical': 'False', 'blank': 'True'}), + 'n_overlay': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_packaged': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'n_versions': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djeuscan.problemreport': { + 'Meta': {'object_name': 'ProblemReport'}, + 'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'subject': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'version': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Version']", 'null': 'True', 'blank': 'True'}) + }, + 'djeuscan.refreshpackagequery': { + 'Meta': {'object_name': 'RefreshPackageQuery'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'priority': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'symmetrical': 'False'}) + }, + 'djeuscan.userprofile': { + 'Meta': {'object_name': 'UserProfile'}, + 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Category']", 'symmetrical': 'False'}), + 'email_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'email_every': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'email_ignore_pre': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_ignore_pre_if_stable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_ignore_pre': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_ignore_pre_if_stable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_portage_info': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'feed_show_adds': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'feed_show_removals': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'feed_upstream_info': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'herds': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Herd']", 'symmetrical': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_email': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Maintainer']", 'symmetrical': 'False'}), + 'overlays': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Overlay']", 'symmetrical': 'False'}), + 'packages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djeuscan.Package']", 'symmetrical': 'False'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}) + }, + 'djeuscan.version': { + 'Meta': {'unique_together': "(['package', 'slot', 'revision', 'version', 'overlay'],)", 'object_name': 'Version'}, + 'alive': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'confidence': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'ebuild_path': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'handler': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_path': ('django.db.models.fields.CharField', [], {'max_length': '256', 'blank': 'True'}), + 'overlay': ('django.db.models.fields.CharField', [], {'default': "'gentoo'", 'max_length': '128', 'db_index': 'True', 'blank': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'packaged': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'slot': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'urls': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'vtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}) + }, + 'djeuscan.versionlog': { + 'Meta': {'object_name': 'VersionLog'}, + 'action': ('django.db.models.fields.IntegerField', [], {}), + 'datetime': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'overlay': ('django.db.models.fields.CharField', [], {'default': "'gentoo'", 'max_length': '128', 'blank': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['djeuscan.Package']"}), + 'packaged': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'revision': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'slot': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'vtype': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}) + }, + 'djeuscan.worldlog': { + 'Meta': {'object_name': 'WorldLog', '_ormbases': ['djeuscan.Log']}, + 'log_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['djeuscan.Log']", 'unique': 'True', 'primary_key': 'True'}) + } + } + + complete_apps = ['djeuscan'] \ No newline at end of file diff --git a/euscanwww/djeuscan/models.py b/euscanwww/djeuscan/models.py index 11e3879..cb0e0e0 100644 --- a/euscanwww/djeuscan/models.py +++ b/euscanwww/djeuscan/models.py @@ -252,6 +252,15 @@ class Overlay(models.Model): class UserProfile(models.Model): + EMAIL_SCAN = 1 + EMAIL_WEEKLY = 2 + EMAIL_MONTHLY = 3 + EMAIL_OPTS = ( + (EMAIL_SCAN, 'On updates'), + (EMAIL_WEEKLY, 'Weekly'), + (EMAIL_MONTHLY, 'Monthly') + ) + user = models.OneToOneField(User) herds = models.ManyToManyField(Herd) maintainers = models.ManyToManyField(Maintainer) @@ -267,8 +276,10 @@ class UserProfile(models.Model): feed_ignore_pre_if_stable = models.BooleanField(default=False) email_activated = models.BooleanField(default=True) + email_every = models.IntegerField(choices=EMAIL_OPTS, default=EMAIL_SCAN) email_ignore_pre = models.BooleanField(default=False) email_ignore_pre_if_stable = models.BooleanField(default=False) + last_email = models.DateTimeField(auto_now_add=True) class Log(models.Model): diff --git a/euscanwww/djeuscan/tasks.py b/euscanwww/djeuscan/tasks.py index 9c989a8..5ec66ea 100644 --- a/euscanwww/djeuscan/tasks.py +++ b/euscanwww/djeuscan/tasks.py @@ -2,15 +2,24 @@ Celery tasks for djeuscan """ +from datetime import datetime + from celery.task import task, group +#import portage + from django.conf import settings from django.core.cache import cache +from django.template.loader import render_to_string +from django.core.mail import send_mail +from django.db.models import Q -import portage +from euscan.version import gentoo_unstable -from djeuscan.models import Package, RefreshPackageQuery +from djeuscan.models import Package, RefreshPackageQuery, UserProfile, \ + VersionLog from djeuscan.processing import scan, misc +from djeuscan.helpers import get_account_versionlogs class TaskFailedException(Exception): @@ -168,9 +177,9 @@ def update_portage_trees(): @task def update_portage(packages=None): - categories = portage.settings.categories + #categories = portage.settings.categories - """ Workaround for celery bug when chaining groups """ + # Workaround for celery bug when chaining groups update_portage_trees() scan_portage(packages=[], purge_packages=True, purge_versions=True, prefetch=True) @@ -205,7 +214,8 @@ def update_upstream(): ( scan_upstream_sub | - update_counters.si(fast=False) + update_counters.si(fast=False) | + send_update_email.si() )() return True @@ -238,7 +248,7 @@ def consume_refresh_queue(locked=False): if not locked and not lock(): return - logger.info('Consumming package refresh request queue...') + logger.info('Consuming package refresh request queue...') try: query = RefreshPackageQuery.objects.all().order_by('-priority')[0] @@ -258,6 +268,76 @@ def consume_refresh_queue(locked=False): kwargs={'locked': True}, countdown=60 ) + +@task(max_retries=10, default_retry_delay=10 * 60) +def send_user_email(address, subject, text): + try: + send_mail( + subject, text, settings.EMAIL_FROM, [address], fail_silently=False + ) + except Exception, exc: + raise send_user_email.retry(exc=exc) + + +@task +def process_emails(profiles): + for profile in profiles: + if not profile.email_activated: + continue + + now = datetime.now() + user = profile.user + + vlogs = get_account_versionlogs(profile) + vlogs = vlogs.filter( + datetime__gt=profile.last_email, + overlay="", # only upstream versions + action=VersionLog.VERSION_ADDED, # only adds + ) + if profile.email_ignore_pre: + vlogs = vlogs.exclude(vtype__in=gentoo_unstable) + if profile.email_ignore_pre_if_stable: + vlogs = vlogs.exclude( + ~Q(package__last_version_gentoo__vtype__in=gentoo_unstable), + vtype__in=gentoo_unstable + ) + + if not vlogs.count(): + continue + + mail_text = render_to_string( + "euscan/accounts/euscan_email.txt", + {"user": user, "vlogs": vlogs} + ) + + send_user_email.delay( + user.email, "euscan updates - %s" % str(now.date()), mail_text + ) + + profile.last_email = now + profile.save(force_update=True) + + +@task +def send_update_email(): + profiles = UserProfile.objects.filter(email_every=UserProfile.EMAIL_SCAN) + group_chunks(process_emails, profiles, settings.TASKS_EMAIL_GROUPS)() + + +@task +def send_weekly_email(): + profiles = UserProfile.objects.filter(email_every=UserProfile.EMAIL_WEEKLY) + group_chunks(process_emails, profiles, settings.TASKS_EMAIL_GROUPS)() + + +@task +def send_monthly_email(): + profiles = UserProfile.objects.filter( + email_every=UserProfile.EMAIL_MONTHLY + ) + group_chunks(process_emails, profiles, settings.TASKS_EMAIL_GROUPS)() + + admin_tasks = [ regen_rrds, update_counters, diff --git a/euscanwww/djeuscan/templates/euscan/accounts/euscan_email.txt b/euscanwww/djeuscan/templates/euscan/accounts/euscan_email.txt new file mode 100644 index 0000000..35dd6d3 --- /dev/null +++ b/euscanwww/djeuscan/templates/euscan/accounts/euscan_email.txt @@ -0,0 +1,6 @@ +Hello {{ user }}, + +euscan news: +{% for vlog in vlogs %} + * {{ vlog.package.category }}/{{ vlog.package.name }}-{{ vlog.version }}-{{ vlog.revision }}:{{ vlog.slot }} [{{ vlog.vtype }}] +{% endfor %} diff --git a/euscanwww/djeuscan/templates/euscan/accounts/preferences.html b/euscanwww/djeuscan/templates/euscan/accounts/preferences.html index 4aa0a37..bde79af 100644 --- a/euscanwww/djeuscan/templates/euscan/accounts/preferences.html +++ b/euscanwww/djeuscan/templates/euscan/accounts/preferences.html @@ -80,6 +80,10 @@ {{ form.email_activated.label_tag }} {{ form.email_activated }} + + {{ form.email_every.label_tag }} + {{ form.email_every }} + {{ form.email_ignore_pre.label_tag }} {{ form.email_ignore_pre }} diff --git a/euscanwww/djeuscan/views.py b/euscanwww/djeuscan/views.py index 5c0d689..2ec9153 100644 --- a/euscanwww/djeuscan/views.py +++ b/euscanwww/djeuscan/views.py @@ -443,6 +443,17 @@ def accounts_preferences(request): prof.feed_upstream_info = form.cleaned_data["feed_upstream_info"] prof.feed_portage_info = form.cleaned_data["feed_portage_info"] + prof.feed_show_adds = form.cleaned_data["feed_show_adds"] + prof.feed_show_removals = form.cleaned_data["feed_show_removals"] + prof.feed_ignore_pre = form.cleaned_data["feed_ignore_pre"] + prof.feed_ignore_pre_if_stable = \ + form.cleaned_data["feed_ignore_pre_if_stable"] + + prof.email_activated = form.cleaned_data["email_activated"] + prof.email_every = form.cleaned_data["email_every"] + prof.email_ignore_pre = form.cleaned_data["email_ignore_pre"] + prof.email_ignore_pre_if_stable = \ + form.cleaned_data["email_ignore_pre_if_stable"] prof.save(force_update=True) @@ -459,6 +470,7 @@ def accounts_preferences(request): "feed_ignore_pre": prof.feed_ignore_pre, "feed_ignore_pre_if_stable": prof.feed_ignore_pre_if_stable, "email_activated": prof.email_activated, + "email_every": prof.email_every, "email_ignore_pre": prof.email_ignore_pre, "email_ignore_pre_if_stable": prof.email_ignore_pre_if_stable, } diff --git a/euscanwww/euscanwww/settings.py b/euscanwww/euscanwww/settings.py index 502536d..6ae512d 100644 --- a/euscanwww/euscanwww/settings.py +++ b/euscanwww/euscanwww/settings.py @@ -222,6 +222,7 @@ ACCOUNT_ACTIVATION_DAYS = 7 RECAPTCHA_PUBLIC_KEY = "" RECAPTCHA_PRIVATE_KEY = "" EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_FROM = 'noreply@iksaif.net' # djeuscan tasks PORTAGE_ROOT = "/" @@ -240,6 +241,7 @@ BROKER_CONNECTION_TIMEOUT = 3600 CELERYD_CONCURRENCY = 4 TASKS_UPSTREAM_GROUPS = 32 +TASKS_EMAIL_GROUPS = 10 CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" @@ -252,6 +254,8 @@ AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', ) +AUTH_PROFILE_MODULE = 'djeuscan.UserProfile' + try: from local_settings import * except ImportError, ex: @@ -264,5 +268,3 @@ except ImportError, ex: os.environ['ROOT'] = PORTAGE_ROOT os.environ['PORTAGE_CONFIGROOT'] = PORTAGE_CONFIGROOT os.environ['EIX_CACHEFILE'] = EIX_CACHEFILE - -AUTH_PROFILE_MODULE = 'djeuscan.UserProfile'