dict–set proxy on self-referential many-to-many SQLAlchemy association - sqlalchemy

In my model, I have a base object class A which holds a set of attributes. Each object of A can be connected to any other object of A through a many-to-many association object Context. This Context class holds a key for every connection.
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
# other attributes
value = Column(Integer)
def __init__(self, value):
self.value = value
class Context(Base):
__tablename__ = "context"
holder_id = Column(Integer, ForeignKey("a.id"), nullable=False, primary_key=True)
attachment_id = Column(Integer, ForeignKey("a.id"), nullable=False, primary_key=True)
key = Column(String, primary_key=True)
holder = relationship(A,
primaryjoin=lambda: Context.holder_id==A.id)
attachment = relationship(A,
primaryjoin=lambda: Context.attachment_id==A.id)
The Context class therefore stores 3-tuples of the form ‘Holder object a1 holds attachment a2 with key k1’.
I now want to have a smart proxy collection on A which groups this relationship on the Context.key such that I can use it as follows:
a1 = A(1)
a1.context["key_1"] = set([A(2)])
a1.context["key_2"] = set([A(3), A(4), A(5)])
a1.context["key_1"].add(A(10))
a100 = A(100)
a100.context = {
"key_1": set([A(101)])
}
How do I have to define context?
I know there is an example for modelling a dict–set proxy in the SQLAlchemy documentation but somehow I am not able to get it to work in a self-referential situation.

Unless I'm blanking a bit (which is possible...gin...) this can't be done with association proxy directly on the relationship because a1.context would need to be a collection where each element has a unique key, then the collection can be a dictionary - but there is no such collection here, as a1 can have many Context objects with the same key. Assoc prox's simple way of reducing a collection of objects to a collection of an attribute on each member object doesn't apply to this.
So if you really want this, and your structure can't change, just do what association proxy does, just in a hardcoded way, which is, build a proxying collection ! actually two, I think. Not too big a deal, just need to turn the crank....quite a bit, make sure you add tests for every manipulation here:
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
import itertools
Base= declarative_base()
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
# other attributes
value = Column(Integer)
def __init__(self, value):
self.value = value
#property
def context(self):
return HolderBySetDict(self)
#context.setter
def context(self, dict_):
toremove = set([ctx for ctx in self.attached_by if ctx.key not in dict_])
toadd = set([Context(key=k, holder=item) for k, v in dict_.items()
for item in itertools.chain(v)])
self.attached_by.update(toadd)
self.attached_by.difference_update(toremove)
class HolderBySetDict(object):
def __init__(self, parent):
self.parent = parent
def __iter__(self):
return iter(self.keys())
def keys(self):
return list(set(ctx.key for ctx in self.parent.attached_by))
def __delitem__(self, key):
toremove = set([ctx for ctx in self.parent.attached_by if ctx.key == key])
self.parent.attached_by.difference_update(toremove)
def __getitem__(self, key):
return HolderBySet(self.parent, key)
def __setitem__(self, key, value):
current = set([ctx for ctx in self.parent.attached_by if ctx.key == key])
toremove = set([ctx for ctx in current if ctx.holder not in value])
toadd = set([Context(key=key,holder=v) for v in value if v not in current])
self.parent.attached_by.update(toadd)
self.parent.attached_by.difference_update(toremove)
# exercises ! for the reader !
#def __contains__(self, key):
#def values(self):
#def items(self):
# ....
class HolderBySet(object):
def __init__(self, parent, key):
self.key = key
self.parent = parent
def __iter__(self):
return iter([ctx.holder for ctx
in self.parent.attached_by if ctx.key == self.key])
def update(self, items):
curr = set([ctx.holder for ctx
in self.parent.attached_by if ctx.key==self.key])
toadd = set(items).difference(curr)
self.parent.attached_by.update(
[Context(key=self.key, holder=item) for item in toadd])
def remove(self, item):
for ctx in self.parent.attached_by:
if ctx.key == self.key and ctx.holder is item:
self.parent.attached_by.remove(ctx)
break
else:
raise ValueError("Value not present")
def add(self, item):
for ctx in self.parent.attached_by:
if ctx.key == self.key and ctx.holder is item:
break
else:
self.parent.attached_by.add(Context(key=self.key, holder=item))
# more exercises ! for the reader !
#def __contains__(self, key):
#def union(self):
#def intersection(self):
#def difference(self):
#def difference_update(self):
# ....
class Context(Base):
__tablename__ = "context"
holder_id = Column(Integer, ForeignKey("a.id"), nullable=False, primary_key=True)
attachment_id = Column(Integer, ForeignKey("a.id"), nullable=False, primary_key=True)
key = Column(String, primary_key=True)
holder = relationship(A,
primaryjoin=lambda: Context.holder_id==A.id)
attachment = relationship(A,
primaryjoin=lambda: Context.attachment_id==A.id,
backref=backref("attached_by", collection_class=set))
a1 = A(1)
a2 = A(2)
a3, a4, a5 = A(3), A(4), A(5)
a1.context["key_1"] = set([a2])
a1.context["key_2"] = set([a3, a4, a5])
assert set([ctx.holder for ctx in a1.attached_by if ctx.key == "key_1"]) == set([a2])
assert set([ctx.holder for ctx in a1.attached_by if ctx.key == "key_2"]) == set([a3, a4, a5])
a10 = A(10)
a1.context["key_1"].add(a10)
print set([ctx.holder for ctx in a1.attached_by if ctx.key == "key_1"])
assert set([ctx.holder for ctx in a1.attached_by if ctx.key == "key_1"]) == set([a2, a10])
a100 = A(100)
a101 = A(101)
a100.context = {
"key_1": set([a101])
}
assert set([ctx.holder for ctx in a100.attached_by]) == set([a101])

Related

How to assign ForeignKey MySQL item in views.py?

When I save my NoteForm, I want to save my form and the "note" field, then I want to create a "tag" for the Note in the NoteTagModel.
At the moment, I create a new tag but it is not assigned to the note. I know that the following code must be wrong:
notetag.id = new_note_parse.id
If I change to:
notetag.note = new_note_parse.id
I receive the following error:
"NoteTagModel.note" must be a "NoteModel" instance.
The below is my views.py:
def notes(request):
note_form = NoteForm
notetag = NoteTagModel()
note_form=NoteForm(request.POST)
if note_form.is_valid():
new_note = note_form.save(commit=False)
new_note_parse = new_note
new_note.save()
notetag.id = new_note_parse.id
notetag.tag = "Test"
notetag.save()
return HttpResponseRedirect(reverse('notes:notes'))
context = {
'note_form' : note_form,
'notes' : NoteModel.objects.all(),
'notes_tag' : NoteTagModel.objects.all(),
}
return render(request, "notes/notes.html", context)
My models.py is:
class NoteModel(models.Model):
note = models.CharField(
max_length = 5000
)
def __str__(self):
return f"{self.note}"
class NoteTagModel(models.Model):
note = models.ForeignKey(
NoteModel,
on_delete=models.CASCADE,
related_name="notes",
blank= False,
null = True,
)
tag = models.CharField(
max_length = 5000
)
def __str__(self):
return f"Note: {self.note} | Tag: {self.tag}"
I have the following as my forms.py:
class NoteForm(ModelForm):
class Meta:
model = NoteModel
fields = [
'note',
]
Change views.py to reflect the below:
note = NoteModel.objects.get(id=new_note_parse.id)
notetag.note = note

Django complete form as signed up user

After you sign up, you are prompted to a page that contains a form used for gathering additional information about the new user and after that it redirects you to the login page. The problem is that the form doesn't submit if i don't specify the {{form.user}} instance in the html file. Probably because the user_id is not recognized by default. When i specify it, the form let me chooses from already existing users, and i would like it to go with the logged in user by default.
views.py
class CreateInfoView(CreateView):
model = AdditionalInfoModel
form_class = AdditionallnfoModelForm
template_name = "user_ski_experience/additional_info.html"
def get_form_kwargs(self):
variable_to_send = super(CreateInfoView, self).get_form_kwargs()
variable_to_send.update({'pk': None})
variable_to_send.update({'pk_user': self.request.user.pk})
return variable_to_send
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse('login')
forms.py
class AdditionallnfoModelForm(forms.ModelForm):
class Meta:
model = AdditionalInfoModel
fields = '__all__'
def __init__(self, pk, *args, **kwargs):
pk_user = kwargs.pop('pk_user')
super(AdditionallnfoModelForm, self).__init__(*args, **kwargs)
self.pk = pk
self.fields['user'].disabled = True
self.fields['user'].initial = pk_user
for el in self.fields:
self.fields[el].label = False
def clean(self):
return self.cleaned_data
How can i solve this ?
class AdditionalInfoModel(models.Model):
objects = None
skill_choices = (('Beginner', 'BEGINNER'),
('Intermediate', 'INTERMEDIATE'),
('Expert', 'EXPERT'))
user = models.OneToOneField(User, on_delete=models.CASCADE)
country = models.CharField(max_length=30, blank=True)
city = models.CharField(max_length=30, blank=True)
assumed_technical_ski_level = models.CharField(max_length=30,
choices=skill_choices)
years_of_experience = models.PositiveIntegerField(blank=True)
money_to_spend = models.PositiveIntegerField(blank=True)
resort_choice = models.ForeignKey(Resorts, on_delete=models.CASCADE,
blank = True, null = True)
def __str__(self):
return self.user.username

Return valid GeoJSON with Flask + GeoAlchemy2 app

Since few days I am wondering how to make my Flask app return valid GeoJSON, here is what I got so far:
models.py
class Building(base):
__tablename__ = 'buildings'
id = Column(Integer, primary_key=True)
district = Column(Unicode)
address = Column(Unicode)
name = Column(Unicode)
building_type = Column(Unicode)
mpoly = Column(Geometry('MULTIPOLYGON'))
building = relationship("User")
# this part is done as answered here: https://stackoverflow.com/questions/41684512/cant-transform-geometry-to-geojson
def building_to_dict(self):
return mapping(shapely.wkb.loads(str(self.mpoly), True))
def __str__(self):
d = dict()
d["id"] = self.id
d["district"] = self.district
d["address"] = self.address
d["name"] = self.name
d["building_type"] = self.building_type
d["mpoly"] = self.building_to_dict()
return shapely.dumps(d)
Now at main file I have following routing:
app.py
#app.route('/geojson')
def get_json():
features = session.query(Building.mpoly.ST_AsGeoJSON()).all()
return jsonify(features)
And here are my two problems:
1)
Returned JSON-like response that looks like following:
"{\"type\":\"MultiPolygon\",\"coordinates\":[[[[16.8933137,52.471446],...,]]]}"
Is not proper GeoJSON.
Features variable before jsonify looks this way:
[('{"type":"MultiPolygon","coordinates":[[[[16.914159616,52.473822807],...,]]]}',)]
2)
How my GeoAlchemy query should look like, to return not just geometry field, but others as well?
Any kind of hints or helps highly appreciated, thanks in advance!
You may use marshmallow-sqlalchemy for creating json like responses.
create
schemas.py
#!schemas.py
from marshmallow import fields
from marshmallow_sqlalchemy import ModelSchema
from app.models import Building
from geoalchemy.shape import to_shape
class BuildingSchema(ModelSchema):
id = fields.Integer()
district = fileds.Unicode()
address = fileds.Unicode()
name = fields.Unicode()
building_type = fields.Unicode()
mpoly = fileds.Method("geom_to_json")
#staticmethod
def geom_to_json(obj):
mpoly = to_shape(obj.mpoly)
return {
lat: mpoly.y,
lon: mpoly.x,
#and so on ...
}
class Meta:
model = Building
exclude = ("mpoly")
bulding_schema = BuildingSchema(many=True)
after this you can use it in your views(routes)
from app.schemas import building_schema
#app.route('/geojson')
def json():
buildings = Buildings.query.all()
response = building_schema.dumps(buildings)
return response

Django AttributeError 'float' object has no attribute 'split'

I installed the module Django Import / Export link
the installation went smoothly. Now when I want to import a file with the extension .xls it shows me the following error:
AttributeError at /admin/xxxx/xxxx/process_import/
'float' Has No object attribute 'split'
Exception Location:
C:\Python34\lib\site-packages\import_export\widgets.py in clean, line 321
When I edit the file here widgets.py source code
def clean(self, value):
if not value:
return self.model.objects.none()
if isinstance(value, float):
ids = [int(value)]
else:
ids = value.split(self.separator)
ids = filter(None, value.split(self.separator))
return self.model.objects.filter(**{
'%s__in' % self.field: ids
})
Here are the lines 321 ids = filter(None, value.split(self.separator))
Django models
class Vehicule(models.Model):
matricule = models.CharField(max_length=200)
modele = models.CharField(max_length=200)
annee = models.IntegerField()
def __str__(self):
return self.matricule
class Ligne(models.Model):
nom = models.CharField(max_length=200)
vehicule = models.ManyToManyField(Vehicule, through='Affecter_Vehicule_Ligne')
def __str__(self):
return str(self.nom)
class Affecter_Vehicule_Ligne(models.Model):
vehicule = models.ForeignKey(Vehicule, on_delete=models.CASCADE)
ligne = models.ForeignKey(Ligne, on_delete=models.CASCADE)
actif = models.BooleanField(default=False)
dateAffectation = models.DateTimeField(null=True)
def __str__(self):
return str(self.dateAffectation)
class Arret(models.Model):
nom = models.CharField(max_length=200, null=True)
latitude = models.CharField(max_length=200, null=True)
longitude = models.CharField(max_length=200, null=True)
lignes = models.ManyToManyField(Ligne, through='Ligne_Arret')
def __str__(self):
return str(self.nom)
class Ligne_Arret(models.Model):
sens = models.CharField(max_length=200)
section = models.BooleanField(default=False)
ligne = models.ForeignKey(Ligne, on_delete=models.CASCADE)
arret = models.ForeignKey(Arret, on_delete=models.CASCADE)
def __str__(self):
return str(self.arret)
Django admin
class VehiculeAdmin(admin.ModelAdmin):
list_display = ('matricule', 'modele', 'annee')
search_fields = ('matricule', 'modele')
class Affecter_Vehicule_LigneAdmin(admin.ModelAdmin):
list_display = ('vehicule', 'dateAffectation', 'ligne')
class ArretAdmin(ImportExportModelAdmin):
pass
class Ligne_ArretAdmin(admin.ModelAdmin):
list_display = ('ligne', 'arret', 'section', 'sens')
admin.site.register(Vehicule, VehiculeAdmin)
admin.site.register(Ligne)
admin.site.register(Affecter_Vehicule_Ligne, Affecter_Vehicule_LigneAdmin)
admin.site.register(Arret, ArretAdmin)
admin.site.register(Ligne_Arret, Ligne_ArretAdmin)
Help me please to solve this problem ???
You are trying to split a float value in this line ids = filter(None, value.split(self.separator))
I think you can just remove this line. As you handle the None case and split before.

Column 'user_id' cannot be null django

I seek to create a post with a form where a registered user creates and inserts the primary key id in the db but this does not give me the following error Column 'user_id' can not be null
This is my models.py
class posts(models.Model):
titulo = models.CharField(max_length=180, unique=True)
slug = models.SlugField(max_length=180, editable=False)
contenido = models.TextField()
categoria = models.ForeignKey(categorias)
user = models.ForeignKey(User)
tags = models.CharField(max_length=200)
creado = models.DateTimeField(auto_now_add=True)
modificado = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.titulo)
super(posts, self).save(*args, **kwargs)
def __str__(self):
return self.titulo
This is my view.py
def addPosts(request):
if request.method == "POST":
form = addPostForm(request.POST)
if form.is_valid():
add = form.save(commit=False)
#add.user = User.objects.get(id=request.user)
add.save()
form.save_m2m()
return HttpResponseRedirect('/')
else:
form = addPostForm ()
ctx = {'form': form}
return render_to_response('posts/add.html', ctx, context_instance=RequestContext(request))
This is forms.py
class addPostForm(forms.ModelForm):
class Meta:
model = posts
exclude = {'slug','user', 'creado'}
some solution to this problem?
Request.user returns the current user object. No need to do a lookup.
add.user = request.user
in your view
If tying to the Django built-in user, you're going to want to do it differently from your model:
user = models.ForeignKey(User)
Consider defining this in your settings and instead, use:
user = models.ForeignKey(settings.AUTH_USER_MODEL)
See the documentation here: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#referencing-the-user-model
This will also future-proof you if you decide to extend the Django base user model in the future.